267 lines
9.0 KiB
C#
267 lines
9.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.ComponentModel.DataAnnotations.Schema;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Linq;
|
|
using LeafWeb.Core.Utility;
|
|
|
|
namespace LeafWeb.Core.Entities
|
|
{
|
|
public class LeafInput : ILeafInput
|
|
{
|
|
public int Id { get; set; }
|
|
|
|
public virtual ICollection<LeafInputFile> InputFiles { get; set; }
|
|
public virtual ICollection<LeafInputData> LeafInputData { get; set; }
|
|
public virtual ICollection<LeafOutputFile> OutputFiles { get; set; }
|
|
|
|
[Required(ErrorMessage = "Name required")]
|
|
public string Name { get; set; }
|
|
|
|
[Required(ErrorMessage = "An email address is required")]
|
|
public string Email { get; set; }
|
|
|
|
[Required(ErrorMessage = "A unique identifier is required")]
|
|
public string Identifier { get; set; }
|
|
|
|
[Required(ErrorMessage = "Site Id required")]
|
|
public string SiteId { get; set; }
|
|
|
|
[Required]
|
|
[Column(TypeName = "VARCHAR")]
|
|
[StringLength(12)]
|
|
[Index("IX_UniqueToken", 1, IsUnique = true)]
|
|
public string UniqueToken { get; set; }
|
|
|
|
// [Required(ErrorMessage = "PhotosynthesisType required")]
|
|
// http://stackoverflow.com/questions/6038541/ef-validation-failing-on-update-when-using-lazy-loaded-required-properties
|
|
public virtual PhotosynthesisType PhotosynthesisType { get; set; }
|
|
|
|
[DataType(DataType.Date)]
|
|
[Required]
|
|
public DateTime Added { get; set; }
|
|
|
|
public LeafInputStatusType CurrentStatus { get; set; }
|
|
|
|
public virtual ICollection<LeafInputStatus> StatusHistory { get; set; }
|
|
|
|
public Priority PendingPriority { get; set; }
|
|
|
|
#region Calculated properties
|
|
|
|
public LeafOutputFile OutputErrorMessage => OutputFiles?.FirstOrDefault(f => f.IsErrorMessage);
|
|
public LeafOutputFile OutputWarningMessage => OutputFiles?.FirstOrDefault(f => f.IsWarningMessage);
|
|
public bool HasOutputFiles => OutputFiles.Any();
|
|
public bool HasLeafChart => OutputFiles.Any(f => f.IsLeafChartFile);
|
|
|
|
public bool IsPending => CurrentStatus == LeafInputStatusType.Pending;
|
|
public bool IsStarting => CurrentStatus == LeafInputStatusType.Starting;
|
|
public bool IsRunning => CurrentStatus == LeafInputStatusType.Running;
|
|
public bool IsFinishing => CurrentStatus == LeafInputStatusType.Finishing;
|
|
public bool IsComplete => CurrentStatus == LeafInputStatusType.Complete;
|
|
public bool IsException => CurrentStatus == LeafInputStatusType.Exception;
|
|
public bool IsCancelPending => CurrentStatus == LeafInputStatusType.CancelPending;
|
|
public bool IsCancelling => CurrentStatus == LeafInputStatusType.Cancelling;
|
|
public bool IsCancelled => CurrentStatus == LeafInputStatusType.Cancelled;
|
|
public bool IsUnresponsive => CurrentStatus == LeafInputStatusType.Unresponsive;
|
|
|
|
public bool IsInProgress => IsStarting
|
|
|| IsRunning
|
|
|| IsFinishing
|
|
|| IsCancelPending
|
|
|| IsCancelling
|
|
|| IsUnresponsive;
|
|
|
|
public bool IsCancellable =>
|
|
IsRunning
|
|
|| IsPending
|
|
|| IsUnresponsive;
|
|
|
|
public bool IsDeletable => !IsInProgress;
|
|
public bool IsAtEndState => IsComplete || IsException || IsCancelled;
|
|
|
|
public DateTime? StartTime
|
|
{
|
|
get
|
|
{
|
|
if (IsPending)
|
|
{
|
|
return null;
|
|
}
|
|
return StatusHistory.FirstOrDefault(s => s.Status == LeafInputStatusType.Starting)?.DateTime;
|
|
}
|
|
}
|
|
|
|
public DateTime? EndTime
|
|
{
|
|
get
|
|
{
|
|
if (!IsAtEndState)
|
|
{
|
|
return null;
|
|
}
|
|
return StatusHistory.FirstOrDefault(
|
|
s =>
|
|
s.Status == LeafInputStatusType.Complete
|
|
|| s.Status == LeafInputStatusType.Exception
|
|
|| s.Status == LeafInputStatusType.Cancelled)?.DateTime;
|
|
}
|
|
}
|
|
|
|
public DateTime LastStatusChange => StatusHistory.Last().DateTime;
|
|
|
|
public TimeSpan TimeInProgress
|
|
{
|
|
get
|
|
{
|
|
var start = StartTime;
|
|
if (!start.HasValue)
|
|
{
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
var end = EndTime;
|
|
if (!end.HasValue)
|
|
{
|
|
return DateTime.Now - start.Value;
|
|
}
|
|
|
|
return end.Value - start.Value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{Id}_{Identifier}";
|
|
}
|
|
|
|
#region Zip
|
|
|
|
/// <summary>
|
|
/// Contains all output files in a zip
|
|
/// </summary>
|
|
public byte[] GetOutputFileZip(LeafOutputFileType? fileType)
|
|
{
|
|
return NewMemoryZipArchive((archive, stream) =>
|
|
CompressOutputFiles(archive, OutputFiles.Where(f => !fileType.HasValue || f.FileType == fileType.Value)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains all input files in a zip
|
|
/// </summary>
|
|
public byte[] GetInputFileZip() =>
|
|
NewMemoryZipArchive((archive, stream) => CompressInputFiles(archive, InputFiles));
|
|
|
|
/// <summary>
|
|
/// Contains all input files files in a zip, ordered into directories
|
|
/// </summary>
|
|
public static byte[] GetInputFilesZip(IEnumerable<LeafInput> leafInputs) =>
|
|
GetLeafFilesZip(
|
|
leafInputs,
|
|
(archive, input, directory) =>
|
|
CompressInputFiles(archive, input.InputFiles, directory));
|
|
|
|
/// <summary>
|
|
/// Contains all output files files in a zip, ordered into directories
|
|
/// </summary>
|
|
public static byte[] GetOutputFilesZip_ToUser(IEnumerable<LeafInput> leafInputs) =>
|
|
GetLeafFilesZip(
|
|
leafInputs,
|
|
(archive, input, directory) =>
|
|
CompressOutputFiles(archive, input.OutputFiles
|
|
.Where(f => f.FileType == LeafOutputFileType.ToUser),
|
|
directory));
|
|
|
|
private static byte[] GetLeafFilesZip(IEnumerable<LeafInput> leafInputs,
|
|
Action<ZipArchive, LeafInput, string> compressAction) =>
|
|
NewMemoryZipArchive((archive, stream) =>
|
|
{
|
|
var inputs = leafInputs.ToList();
|
|
|
|
var leftBehindFiles = new List<string>();
|
|
|
|
foreach (var leafInput in inputs)
|
|
{
|
|
if (stream.Length > 2560000L)
|
|
{
|
|
leftBehindFiles.Add(leafInput.Identifier.FilterValidFilename());
|
|
continue;
|
|
}
|
|
|
|
var directory = $"{leafInput.Added:yyyy-dd-MM--HH-mm}_{leafInput.Identifier.FilterValidFilename()}";
|
|
compressAction(archive, leafInput, directory);
|
|
}
|
|
|
|
if (leftBehindFiles.Count > 0)
|
|
{
|
|
leftBehindFiles.Insert(0, "NOTICE. The following LeafInput were not included due to file size issue.");
|
|
leftBehindFiles.Insert(1, "---");
|
|
CompressStringToFile(archive, leftBehindFiles.Join(Environment.NewLine), "README.TXT");
|
|
}
|
|
});
|
|
|
|
private static byte[] NewMemoryZipArchive(Action<ZipArchive, MemoryStream> addFiles)
|
|
{
|
|
using (var compressedFileStream = new MemoryStream())
|
|
{
|
|
using (var archive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, true))
|
|
{
|
|
addFiles(archive, compressedFileStream);
|
|
}
|
|
// ZipArchive must be disposed before returning the data
|
|
return compressedFileStream.ToArray();
|
|
}
|
|
}
|
|
|
|
private static long CompressInputFiles(ZipArchive archive, IEnumerable<LeafInputFile> inputFiles,
|
|
string directory = "")
|
|
{
|
|
var contentsLength = 0L;
|
|
foreach (var inputFile in inputFiles)
|
|
{
|
|
contentsLength += inputFile.Contents.LongLength;
|
|
var entry = archive.CreateEntry(Path.Combine(directory, inputFile.Filename));
|
|
using (var originalFileStream = new MemoryStream(inputFile.Contents))
|
|
using (var entryStream = entry.Open())
|
|
originalFileStream.CopyTo(entryStream);
|
|
}
|
|
|
|
return contentsLength;
|
|
}
|
|
|
|
private static long CompressOutputFiles(ZipArchive archive, IEnumerable<LeafOutputFile> outputFiles,
|
|
string directory = "")
|
|
{
|
|
var contentsLength = 0L;
|
|
foreach (var outputFile in outputFiles)
|
|
{
|
|
contentsLength += outputFile.FileContents.Contents.LongLength;
|
|
var entry = archive.CreateEntry(Path.Combine(directory, outputFile.Filename));
|
|
using (var originalFileStream = new MemoryStream(outputFile.FileContents.Contents))
|
|
using (var entryStream = entry.Open())
|
|
originalFileStream.CopyTo(entryStream);
|
|
}
|
|
return contentsLength;
|
|
}
|
|
|
|
private static void CompressStringToFile(ZipArchive archive, string contents, string filename)
|
|
{
|
|
var entry = archive.CreateEntry(filename);
|
|
using (var writer = new StreamWriter(entry.Open()))
|
|
{
|
|
writer.Write(contents);
|
|
}
|
|
}
|
|
|
|
public int GetOutputFileSizeSum()
|
|
{
|
|
return OutputFiles.Sum(o => o.FileContents.Contents.Length);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |