Saxonite 1.2.11.2
This commit is contained in:
@@ -98,7 +98,9 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
public String DateTimeSeparator { get; set; } = " ";
|
||||
public String TimeFormat { get; set; } = "HH:mm";
|
||||
public DataValue.DataType DataType { get; set; } = DataValue.DataType.String;
|
||||
public bool IsNumeric { get { return DataType == DataValue.DataType.Long || DataType == DataValue.DataType.Double || DataType == DataValue.DataType.Decimal; } }
|
||||
public bool Resizable { get; set; } = true;
|
||||
public bool WrapText { get; set; } = false;
|
||||
public bool Hidden { get; set; } = false;
|
||||
public bool Frozen { get; set; } = false;
|
||||
// Drop-down configuration.
|
||||
@@ -107,12 +109,11 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
public bool ExclusiveValues { get; set; } = true;
|
||||
/// <summary>User entered values (Exclusive=false) are converted if corresponding value found in list.</summary>
|
||||
public bool MatchCase { get; set; } = true;
|
||||
// Edit configuration:
|
||||
// Edit configuration.
|
||||
public int MaxLength { get; set; } = 0;
|
||||
public bool ReadOnly { get; set; } = false;
|
||||
public bool Uppercase { get; set; } = false;
|
||||
public bool Lowercase { get; set; } = false;
|
||||
public bool Numeric { get; set; } = false;
|
||||
public bool Password { get; set; } = false;
|
||||
// Sort attributes.
|
||||
/// <summary>Sort mode. Default: 'On'.</summary>
|
||||
|
||||
@@ -172,6 +172,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
/// <summary>Return value interpretted as boolean.</summary>
|
||||
public bool ToBool ()
|
||||
{
|
||||
if (value == null) return false;
|
||||
@@ -190,6 +191,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>Return explicit boolean value or false for other types.</summary>
|
||||
public bool AsBool ()
|
||||
{
|
||||
// Convert to specific type of data - fails if type is not correct:
|
||||
@@ -369,10 +371,58 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
// Data Value list extension functions.
|
||||
|
||||
public static class DataValueList
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Population.
|
||||
|
||||
/// <summary>Add a String value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, String value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add a boolean value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, bool value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add an integer value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, int value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add a long value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, long value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add a double value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, double value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add a decimal value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, decimal value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
/// <summary>Add a date time value to the list.</summary>
|
||||
public static void Add(this List<DataValue> values, DateTime value)
|
||||
{
|
||||
values.Add(new DataValue(value));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Conversion.
|
||||
|
||||
/// <summary>Convert all data values to strings.</summary>
|
||||
public static List<String> ToStringList (this List<DataValue> values)
|
||||
{
|
||||
@@ -381,6 +431,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
stringValues.Add(values.ToString());
|
||||
return stringValues;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the data values to raw objects.
|
||||
/// <para>eg. For population to Excel spreadsheet cells.</para>
|
||||
@@ -409,6 +460,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Numeric - only with compatible numeric values.
|
||||
|
||||
/// <summary>Minimum numeric value in the list.</summary>
|
||||
public static DataValue GetMinValue (this List<DataValue> values)
|
||||
{
|
||||
@@ -425,6 +477,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
}
|
||||
return minValue;
|
||||
}
|
||||
|
||||
/// <summary>Maximum numeric value in the list.</summary>
|
||||
public static DataValue GetMaxValue (this List<DataValue> values)
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Sarand.Net.Standard.Base.Core
|
||||
// Disposal.
|
||||
~SacsDisposable ()
|
||||
{
|
||||
Dispose(false);
|
||||
Dispose(!disposed);
|
||||
}
|
||||
public void Dispose ()
|
||||
{
|
||||
|
||||
@@ -38,8 +38,20 @@ namespace Sarand.Net.Standard.Base.Helpers
|
||||
public new String PreferredContentType { set { base.PreferredContentType = value; } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
/// <summary>Excel legacy - xls.</summary>
|
||||
public static readonly String ContentType_Excel_Legacy = "application/vnd.ms-excel";
|
||||
/// <summary>Excel open - xlsx.</summary>
|
||||
public static readonly String ContentType_Excel_Open = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
/// <summary>Excel macro enabled - xlsm.</summary>
|
||||
public static readonly String ContentType_Excel_MacroEnabled = "application/vnd.ms-excel.sheet.macroEnabled.12";
|
||||
/// <summary>Excel binary macro enabled - xlsb.</summary>
|
||||
public static readonly String ContentType_Excel_BinaryMacroEnabled = "application/vnd.ms-excel.sheet.binary.macroEnabled.12";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
static String ContentType_Generic = "application/octet-stream";
|
||||
private static readonly List<ContentTypeData> contentTypes;
|
||||
private static FileExtensionContentTypeProvider provider = null;
|
||||
private static readonly HashSet<Sacs.MimeType> allowedMimeTypes_CV = new HashSet<Sacs.MimeType>()
|
||||
@@ -87,8 +99,8 @@ namespace Sarand.Net.Standard.Base.Helpers
|
||||
new ContentTypeData_Internal("docx", "Microsoft Word (OpenXML)", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Sacs.MimeType.MsDocx),
|
||||
new ContentTypeData_Internal("odt", "OpenDocument text document", "application/vnd.oasis.opendocument.text", Sacs.MimeType.OpenDocText),
|
||||
// Spreadsheets.
|
||||
new ContentTypeData_Internal("xls", "Microsoft Excel", "application/vnd.ms-excel", Sacs.MimeType.MsXls),
|
||||
new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Sacs.MimeType.MsXlsx),
|
||||
new ContentTypeData_Internal("xls", "Microsoft Excel", ContentType_Excel_Legacy, Sacs.MimeType.MsXls),
|
||||
new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", ContentType_Excel_Open, Sacs.MimeType.MsXlsx),
|
||||
new ContentTypeData_Internal("ods", "OpenDocument spreadsheet document", "application/vnd.oasis.opendocument.spreadsheet", Sacs.MimeType.OpenDocSpreadsheet),
|
||||
// Miscellaneous.
|
||||
new ContentTypeData_Internal("aac", "AAC audio", "audio/aac"),
|
||||
@@ -156,7 +168,7 @@ namespace Sarand.Net.Standard.Base.Helpers
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
/// <summary>Generic content type used for 'unknown' document types.</summary>
|
||||
public static String ContentTypeDefault { get; } = "application/octet-stream";
|
||||
public static String ContentTypeDefault { get; } = ContentType_Generic;
|
||||
/// <summary>Selection of comon content types.</summary>
|
||||
public static List<ContentTypeData> ContentTypes { get { return new List<ContentTypeData>(contentTypes); } }
|
||||
public static FileExtensionContentTypeProvider Provider
|
||||
@@ -223,6 +235,20 @@ namespace Sarand.Net.Standard.Base.Helpers
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
/// <summary>Convert the enumerated Mime to content type.</summary>
|
||||
public static String GetContentTypeFromMimeType(Sacs.MimeType mime)
|
||||
{
|
||||
if (mime == Sacs.MimeType.Unknown) return ContentTypeDefault;
|
||||
ContentTypeData contentTypeData = contentTypes.Find(item => item.Mime.Equals(mime));
|
||||
if (contentTypeData != null)
|
||||
return contentTypeData.ContentType;
|
||||
return ContentTypeDefault;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Mime type.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/// <summary>Convert the content type to enumerated Mime.</summary>
|
||||
public static Sacs.MimeType GetMimeTypeFromContentType(this String contentType)
|
||||
{
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Tasks;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using static Sarand.Net.Standard.Base.Procs.SaProcess_Base;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public interface ISacsProcessNotifier
|
||||
{
|
||||
void OnStartProcess ();
|
||||
void SetProgressInfo (List<String> progressInfo);
|
||||
void OnEndProcess ();
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public abstract class SaProcess_Base : SacsDisposable
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Exceptions.
|
||||
[Serializable]
|
||||
public class InProgressException : Exception
|
||||
{
|
||||
public InProgressException() { }
|
||||
public InProgressException(string message) : base(message) { }
|
||||
public InProgressException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process options and groups - for configuration of process to run.
|
||||
// See Sarand.Win.Forms.Utility.SaForm_SelectOptions.
|
||||
public class OptionData
|
||||
{
|
||||
public OptionData (TypeCodeDesc option)
|
||||
public OptionData(TypeCodeDesc option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
@@ -33,7 +41,7 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
}
|
||||
public class GroupData
|
||||
{
|
||||
public GroupData (TypeCodeDesc optionGroup)
|
||||
public GroupData(TypeCodeDesc optionGroup)
|
||||
{
|
||||
OptionGroup = optionGroup;
|
||||
}
|
||||
@@ -55,13 +63,13 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
}
|
||||
public class Config
|
||||
{
|
||||
protected Config ()
|
||||
protected Config()
|
||||
{
|
||||
}
|
||||
/// <summary>Identifies the type of process.</summary>
|
||||
public ProcessType ProcessType { get; set; } = ProcessType.Unknown;
|
||||
/// <summary>Is the process to be run in 'Test' mode.</summary>
|
||||
public bool TestProcess{ get; set; } = SacsSystem.DebugMode;
|
||||
public bool TestProcess { get; set; } = SacsSystem.DebugMode;
|
||||
/// <summary>Process imports data.</summary>
|
||||
public bool Import { get { return ProcessType == ProcessType.ImportData || ProcessType == ProcessType.ImportExportData; } }
|
||||
/// <summary>Process exports data.</summary>
|
||||
@@ -74,28 +82,131 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
public bool Automated { get; set; } = false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process control definitions.
|
||||
|
||||
public class ProcessMessage
|
||||
{
|
||||
public ProcessMessage(String message, SaMessageLevel level = SaMessageLevel.Info)
|
||||
{
|
||||
this.Message = message;
|
||||
this.Level = level;
|
||||
}
|
||||
public String Message { get; }
|
||||
public SaMessageLevel Level { get; }
|
||||
public int Instances { get; internal set; }
|
||||
}
|
||||
|
||||
private class ProcessControlData
|
||||
{
|
||||
// Interrupt.
|
||||
private class InterruptData
|
||||
{
|
||||
public readonly Object lockable = new object();
|
||||
private bool interrupted = false;
|
||||
public bool Interrupted { get { lock (lockable) { return interrupted; } } set { lock (lockable) { interrupted = value; } } }
|
||||
}
|
||||
private InterruptData interrupt { get; } = new InterruptData();
|
||||
public bool Interrupted { get { return interrupt.Interrupted; } set { interrupt.Interrupted = value; } }
|
||||
|
||||
// Process messages.
|
||||
private class ProcessMessagesData
|
||||
{
|
||||
public readonly Object lockable = new object();
|
||||
public List<ProcessMessage> messages { get; } = new List<ProcessMessage>();
|
||||
}
|
||||
private ProcessMessagesData processMessages { get; } = new ProcessMessagesData();
|
||||
/// <summary>Convert the message levele to one of the 3 values used with Process Messages: Info, Warning, Error.</summary>
|
||||
public static SaMessageLevel GetLevel(SaMessageLevel level)
|
||||
{
|
||||
if (level == SaMessageLevel.Error || level == SaMessageLevel.Fatal || level == SaMessageLevel.Exception)
|
||||
level = SaMessageLevel.Error;
|
||||
else if (level == SaMessageLevel.Warning)
|
||||
level = SaMessageLevel.Warning;
|
||||
else
|
||||
level = SaMessageLevel.Info;
|
||||
return level;
|
||||
}
|
||||
/// <summary>Add a message of the specified level (Info, Warning, Error).</summary>
|
||||
public bool AddProcessMessage(String message, SaMessageLevel level)
|
||||
{
|
||||
message = StringUtil.TrimWhitespace(message);
|
||||
if (StringUtil.IsBlank(message)) return false;
|
||||
level = GetLevel(level);
|
||||
lock (processMessages.lockable)
|
||||
{
|
||||
ProcessMessage procMsg = processMessages.messages.Find(item => item.Message.EqualsCI(message) && item.Level == level);
|
||||
// Duplicate messages increment the 'Instances' value.
|
||||
if (procMsg != null)
|
||||
procMsg.Instances++;
|
||||
// Otherwise add a new message to the list.
|
||||
else processMessages.messages.Add(new ProcessMessage(message, level));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/// <summary>Are there any message of the specified level (Info, Warning, Error).</summary>
|
||||
public bool HasProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
return processMessages.messages.Exists(item => item.Level == GetLevel(level));
|
||||
}
|
||||
/// <summary>Get the first message of the specified level (Info, Warning, Error).</summary>
|
||||
public String GetProcessMessage(SaMessageLevel level)
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
return processMessages.messages.Find(item => item.Level == GetLevel(level))?.Message ?? "";
|
||||
}
|
||||
/// <summary>Return all process messages.</summary>
|
||||
public List<ProcessMessage> GetProcessMessages()
|
||||
{
|
||||
List<ProcessMessage> messages = new List<ProcessMessage>();
|
||||
lock (processMessages.lockable)
|
||||
messages.AddRange(processMessages.messages);
|
||||
return messages;
|
||||
}
|
||||
/// <summary>Return all process messages of the specified Level (Info, Warning, Error).</summary>
|
||||
public List<ProcessMessage> GetProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
List<ProcessMessage> messages = new List<ProcessMessage>();
|
||||
lock (processMessages.lockable)
|
||||
messages.AddRange(processMessages.messages.FindAll(item => item.Level == GetLevel(level)));
|
||||
return messages;
|
||||
}
|
||||
/// <summary>Clear process messages.</summary>
|
||||
public void ClearProcessMessages()
|
||||
{
|
||||
lock (processMessages.lockable)
|
||||
processMessages.messages.Clear();
|
||||
}
|
||||
|
||||
// Task control data.
|
||||
private SacsTaskData taskData = null;
|
||||
public SacsTaskData TaskData { get { return taskData; } set { if (taskData != null) taskData.Dispose(); taskData = value; } }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
private String processError = "";
|
||||
private ProcessControlData Control { get; } = new ProcessControlData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaProcess_Base (Config config)
|
||||
protected SaProcess_Base(Config config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
protected override void Dispose (bool disposing)
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Clear process data to dispose.
|
||||
Control.TaskData = null;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public bool DebugMode { get { return SacsSystem.DebugMode; } }
|
||||
public ISacsProcessNotifier ProcessNotifier { get; set; } = null;
|
||||
public bool Processing { get; protected set; } = false;
|
||||
public bool ProcessEnded { get; protected set; } = false;
|
||||
public DateTime StartedAt { get; private set; } = DateTime.MinValue;
|
||||
@@ -120,56 +231,195 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
public virtual List<StringPair> ProcessingStatistics { get { return new List<StringPair>(); } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run process inline.
|
||||
public bool RunProcess ()
|
||||
// Run process asynchronously.
|
||||
private class AsyncProcessData : SacsDisposable
|
||||
{
|
||||
String fn = GetType().Name+".RunProcess";
|
||||
if (ProcessNotifier == null)
|
||||
throw new NotSupportedException("Process Notifier must be specified");
|
||||
public AsyncProcessData(Task task)
|
||||
{
|
||||
this.Task = task;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) { }
|
||||
}
|
||||
public Task Task { get; }
|
||||
}
|
||||
private AsyncProcessData AsyncData { get; set; }
|
||||
public bool IsAsync { get { return AsyncData != null; } }
|
||||
private void ClearAsyncData()
|
||||
{
|
||||
if (AsyncData != null)
|
||||
{
|
||||
lock (AsyncData)
|
||||
{
|
||||
AsyncData.Dispose();
|
||||
AsyncData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Run the process asynchronously.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InProgressException">If process is already running.</exception>
|
||||
public async void RunAsync()
|
||||
{
|
||||
if (AsyncData != null)
|
||||
throw new InProgressException();
|
||||
Control.TaskData = new SacsTaskData();
|
||||
// Run on separate thread.
|
||||
Task task = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Execute the process.
|
||||
RunProcess();
|
||||
}
|
||||
catch (OperationCanceledException exc)
|
||||
{
|
||||
SaLogger.Warn(exc.GetType().Name + " during asynchronous processing on " + GetType().Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clear processing data.
|
||||
ClearAsyncData();
|
||||
}
|
||||
}, Control.TaskData.CancellationToken);
|
||||
AsyncData = new AsyncProcessData(task);
|
||||
task.Start();
|
||||
await task;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run process inline.
|
||||
public bool RunProcess()
|
||||
{
|
||||
String fn = GetType().Name + ".RunProcess";
|
||||
// Prepare process.
|
||||
bool runProcess = false;
|
||||
Reset();
|
||||
if (Control.TaskData == null)
|
||||
Control.TaskData = new SacsTaskData();
|
||||
Processing = true;
|
||||
StartedAt = DateTime.Now;
|
||||
|
||||
// Process starting.
|
||||
try
|
||||
{
|
||||
// Notify start of process and log/ignore exceptions.
|
||||
ProcessNotifier.OnStartProcess();
|
||||
InvokeProcessStarting();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + " OnStartProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " ProcessStarting: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
|
||||
// Process running.
|
||||
try
|
||||
{
|
||||
// Run process.
|
||||
OnProcessStarting();
|
||||
runProcess = Run();
|
||||
OnProcessEnding();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SetProcessError(exc.GetType().Name + ": " + exc.Message);
|
||||
SaLogger.Error(fn + " " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
// Processing completed.
|
||||
FinishedAt = DateTime.Now;
|
||||
Processing = false;
|
||||
ProcessEnded = true;
|
||||
finally
|
||||
{
|
||||
// Processing completed.
|
||||
FinishedAt = DateTime.Now;
|
||||
Processing = false;
|
||||
ProcessEnded = true;
|
||||
Control.TaskData = null;
|
||||
}
|
||||
|
||||
// Process completed.
|
||||
try
|
||||
{
|
||||
// Notify end of process and log/ignore exceptions.
|
||||
ProcessNotifier.OnEndProcess();
|
||||
// Notify completion of process and log/ignore exceptions.
|
||||
InvokeProcessCompleted();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Error(fn + " OnEndProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
String msg = exc.GetType().Name + ": " + exc.Message;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Error(fn + " ProcessCompleted: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true);
|
||||
}
|
||||
return runProcess;
|
||||
}
|
||||
protected abstract bool Run ();
|
||||
protected virtual void OnProcessStarting () { }
|
||||
protected virtual void OnProcessEnding () { }
|
||||
protected abstract bool Run();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public async Task StopProcessAsync(int timeout = 50)
|
||||
{
|
||||
if (!Processing) return;
|
||||
if (AsyncData != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Cancel the task.
|
||||
Control.TaskData.TriggerCancellation();
|
||||
// Wait for task termination or timeout.
|
||||
if (await Task.WhenAny(AsyncData.Task, Task.Delay(Math.Max(20, Math.Min(1000, timeout)))) != AsyncData.Task)
|
||||
{
|
||||
// Timeout completed before task - force termination.
|
||||
Control.TaskData.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Control.TaskData = null;
|
||||
ClearAsyncData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Processing events.
|
||||
|
||||
// Processing Starting.
|
||||
public class ProcessStartingEventArgs : EventArgs { }
|
||||
public event EventHandler<ProcessStartingEventArgs> OnProcessStarting;
|
||||
protected virtual void InvokeProcessStarting()
|
||||
{
|
||||
if (OnProcessStarting != null)
|
||||
{
|
||||
ProcessStartingEventArgs args = new ProcessStartingEventArgs();
|
||||
OnProcessStarting(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Process Info Updated.
|
||||
public class ProgressInfoUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public List<String> ProgressInfo { get; } = new List<string>();
|
||||
}
|
||||
public event EventHandler<ProgressInfoUpdatedEventArgs> OnProgressInfoUpdated;
|
||||
protected virtual void InvokeProgressInfoUpdated(List<String> progressInfo)
|
||||
{
|
||||
if (OnProgressInfoUpdated != null)
|
||||
{
|
||||
ProgressInfoUpdatedEventArgs args = new ProgressInfoUpdatedEventArgs();
|
||||
args.ProgressInfo.AddRange(progressInfo);
|
||||
OnProgressInfoUpdated(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing Completed.
|
||||
public class ProcessCompletedEventArgs : EventArgs { }
|
||||
public event EventHandler<ProcessCompletedEventArgs> OnProcessCompleted;
|
||||
protected virtual void InvokeProcessCompleted()
|
||||
{
|
||||
if (OnProcessCompleted != null)
|
||||
{
|
||||
ProcessCompletedEventArgs args = new ProcessCompletedEventArgs();
|
||||
OnProcessCompleted(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Process control.
|
||||
@@ -177,15 +427,15 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HasProcessError()) return false;
|
||||
if (isInterrupted) return false;
|
||||
if (HasErrorMessage()) return false;
|
||||
if (Control.Interrupted) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private void Reset ()
|
||||
private void Reset()
|
||||
{
|
||||
ClearProcessError();
|
||||
isInterrupted = false;
|
||||
Control.ClearProcessMessages();
|
||||
Control.Interrupted = false;
|
||||
ProcessEnded = false;
|
||||
StartedAt = DateTime.MinValue;
|
||||
FinishedAt = DateTime.MinValue;
|
||||
@@ -193,36 +443,70 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Progress information.
|
||||
protected void SetProgressInfo (String progressInfo)
|
||||
protected void SetProgressInfo(String progressInfo)
|
||||
{
|
||||
SetProgressInfo(StringListUtil.CreateList(progressInfo));
|
||||
}
|
||||
protected void SetProgressInfo (List<String> progressInfo)
|
||||
protected void SetProgressInfo(List<String> progressInfo)
|
||||
{
|
||||
if (ProcessNotifier != null)
|
||||
ProcessNotifier.SetProgressInfo(progressInfo);
|
||||
InvokeProgressInfoUpdated(progressInfo);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Error handling.
|
||||
public bool HasProcessError () { return !StringUtil.IsEmpty(processError); }
|
||||
public String GetProcessError () { return StringUtil.EmptyOnNull(processError); }
|
||||
public void ClearProcessError () { processError = ""; }
|
||||
/// <summary>Record the processing error - always returns false.</summary>
|
||||
public bool SetProcessError (String error)
|
||||
// Process messages.
|
||||
/// <summary>Add a message of the specified level (Info, Warning, Error).</summary>
|
||||
protected void AddMessage(String message, SaMessageLevel level)
|
||||
{
|
||||
processError = StringUtil.EmptyOnNull(error);
|
||||
if (Control.AddProcessMessage(message, level))
|
||||
SaLogger.Log(GetType().Name + " " + message, ProcessControlData.GetLevel(level));
|
||||
}
|
||||
protected void AddInfoMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Info);
|
||||
}
|
||||
protected void AddWarningMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Warning);
|
||||
}
|
||||
/// <summary>Add the error message and always return false.</summary>
|
||||
protected bool AddErrorMessage(String message)
|
||||
{
|
||||
AddMessage(message, SaMessageLevel.Error);
|
||||
return false;
|
||||
}
|
||||
/// <summary>Are there any error messages.</summary>
|
||||
public bool HasErrorMessage()
|
||||
{
|
||||
return Control.HasProcessMessages(SaMessageLevel.Error);
|
||||
}
|
||||
/// <summary>Get the first error message.</summary>
|
||||
public String GetErrorMessage()
|
||||
{
|
||||
return Control.GetProcessMessage(SaMessageLevel.Error);
|
||||
}
|
||||
/// <summary>Return all process messages.</summary>
|
||||
public List<ProcessMessage> GetProcessMessages()
|
||||
{
|
||||
return Control.GetProcessMessages();
|
||||
}
|
||||
/// <summary>Return all process messages of the specified Level (Info, Warning, Error).</summary>
|
||||
public List<ProcessMessage> GetProcessMessages(SaMessageLevel level)
|
||||
{
|
||||
return Control.GetProcessMessages(level);
|
||||
}
|
||||
/// <summary>Clear process messages.</summary>
|
||||
public void ClearProcessMessages()
|
||||
{
|
||||
Control.ClearProcessMessages();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Interruption.
|
||||
private bool isInterrupted = false;
|
||||
public bool IsInterrupted
|
||||
{
|
||||
get { return isInterrupted; }
|
||||
get { return Control.Interrupted; }
|
||||
}
|
||||
public bool Interrupt ()
|
||||
public bool Interrupt()
|
||||
{
|
||||
if (!config.AllowInterruption)
|
||||
{
|
||||
@@ -230,10 +514,10 @@ namespace Sarand.Net.Standard.Base.Procs
|
||||
return false;
|
||||
}
|
||||
if (config.ConfirmInterruption && !IsInterruptionConfirmed()) return false;
|
||||
isInterrupted = true;
|
||||
Control.Interrupted = true;
|
||||
return true;
|
||||
}
|
||||
protected virtual bool IsInterruptionConfirmed ()
|
||||
protected virtual bool IsInterruptionConfirmed()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Sarand.Net.Standard.Base.Tasks
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SacsTaskData (CancellationToken? cancellationToken)
|
||||
public SacsTaskData (CancellationToken? cancellationToken = null)
|
||||
{
|
||||
if (cancellationToken != null)
|
||||
{
|
||||
@@ -49,18 +49,22 @@ namespace Sarand.Net.Standard.Base.Tasks
|
||||
// Properties.
|
||||
public bool Debug { get; set; }
|
||||
|
||||
/// <summary>Token to allow the async request to be cancelled.</summary>
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// C a n c e l l a t i o n
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Token to allow the async request to be cancelled.</summary>
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>Gets whether cancellation has been requested.</summary>
|
||||
public bool IsCancellationRequested { get { return CancellationToken.IsCancellationRequested; } }
|
||||
|
||||
/// <summary>Throws a System.OperationCanceledException if this token has had cancellation requested.</summary>
|
||||
public void ThrowIfCancellationRequested()
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>Can cancellation be triggered here?</summary>
|
||||
public bool CanBeCancelled { get { return cancellationTokenSource != null; } }
|
||||
public bool TriggerCancellation ()
|
||||
|
||||
@@ -79,7 +79,8 @@ namespace Sarand.Net.Standard.Base.Utils
|
||||
/// </summary>
|
||||
public static SixLabors.ImageSharp.Image LoadImage(byte[] imageData, out IImageFormat format)
|
||||
{
|
||||
var image = Image.Load<Rgba32>(imageData, out format);
|
||||
var image = Image.Load<Rgba32>(imageData);
|
||||
format = Image.DetectFormat(imageData);
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ namespace Sarand.Net.Standard.Base.Utils
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(imageFormat);
|
||||
var imageEncoder = image.Configuration.ImageFormatsManager.GetEncoder(imageFormat);
|
||||
image.Save(ms, imageEncoder);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
@@ -26,32 +27,24 @@ namespace Sarand.Net.Standard.Base.Utils
|
||||
if (Object.ReferenceEquals(source, null))
|
||||
return default(T);
|
||||
|
||||
IFormatter formatter = new BinaryFormatter();
|
||||
Stream stream = new MemoryStream();
|
||||
using (stream)
|
||||
{
|
||||
formatter.Serialize(stream, source);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return (T)formatter.Deserialize(stream);
|
||||
}
|
||||
// initialize inner objects individually
|
||||
// for example in default constructor some list property initialized with some values,
|
||||
// but in 'source' these items are cleaned -
|
||||
// without ObjectCreationHandling.Replace default constructor values will be added to result
|
||||
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Serialize.
|
||||
public static String Serialize<T>(T source)
|
||||
// Serialize using JSON.
|
||||
public static String ToJson<T>(T source)
|
||||
{
|
||||
if (!typeof(T).IsSerializable) throw new ArgumentException("The type must be serializable.", "source");
|
||||
// Don't serialize a null object, simply return the default for that object
|
||||
if (Object.ReferenceEquals(source, null)) return "";
|
||||
// Serialise to String.
|
||||
IFormatter formatter = new BinaryFormatter();
|
||||
using (Stream stream = new MemoryStream())
|
||||
using (StreamReader streamReader = new StreamReader(stream))
|
||||
{
|
||||
formatter.Serialize(stream, source);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return streamReader.ReadToEnd();
|
||||
}
|
||||
// Serialise to JSON String.
|
||||
return JsonConvert.SerializeObject(source);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
@@ -231,7 +231,20 @@ namespace Sarand.Net.Standard.Base.Utils
|
||||
Regex regexMultipleInternal = new Regex("[" + Regex.Escape(new String(trimChars)) + "]{2,}", RegexOptions.None);
|
||||
return regexMultipleInternal.Replace(value, replacement);
|
||||
}
|
||||
|
||||
private static Regex regexWhitespace = new Regex("\\s+", RegexOptions.Compiled);
|
||||
/// <summary>
|
||||
/// Trim all leading and trailling whitespace and replace internal whitespace with a single space.
|
||||
/// </summary>
|
||||
public static String TrimWhitespace(this String value)
|
||||
{
|
||||
return regexWhitespace.Replace(value, " ").Trim();
|
||||
}
|
||||
|
||||
private static Regex regexMultipleSpaces = new Regex("[ ]{2,}", RegexOptions.Compiled);
|
||||
/// <summary>
|
||||
/// Replace multiple spaces with a single space.
|
||||
/// </summary>
|
||||
public static String TrimInternalSpaces(this String value)
|
||||
{
|
||||
return regexMultipleSpaces.Replace(value, " ");
|
||||
@@ -540,6 +553,7 @@ namespace Sarand.Net.Standard.Base.Utils
|
||||
if (value.ToDouble() != 0.0) return true;
|
||||
if (value.Left(1).EqualsCI("Y")) return true;
|
||||
if (value.EqualsCI("True")) return true;
|
||||
if (value.EqualsCI("On")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,82 @@
|
||||
namespace Sarand.Net.Standard.Base.Utils
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Sarand.Net.Standard.Base.Utils
|
||||
{
|
||||
//=======================================================================
|
||||
public class SysUtil
|
||||
public static class SysUtil
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
/// <summary>Get calling code's class name.</summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCallerClassName()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stackFrame = new StackTrace().GetFrame(1);
|
||||
var methodInfo = stackFrame.GetMethod();
|
||||
return methodInfo.ReflectedType.Name.TrimWhitespace();
|
||||
}
|
||||
catch (Exception) { }
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>Get calling code's fucntion name.</summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCallerMemberName([CallerMemberName] string memberName = "")
|
||||
{
|
||||
return memberName;
|
||||
}
|
||||
|
||||
/// <summary>Get calling code's file path.</summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCallerFilePath([CallerFilePath] string filePath = "")
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/// <summary>Get calling code's line number.</summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static int GetCallerLineNumber([CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
private SysUtil () { }
|
||||
/// <summary>Shortcut to GetCallerClassMethod() - returns [class.method] for calling code.</summary>
|
||||
public static string Fn { get { return GetCallerClassMethod(2); } }
|
||||
|
||||
/// <summary>Return identity of calling method as [class.method].</summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCallerClassMethod(int stackFrameIdx = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stackFrame = new StackTrace().GetFrame(stackFrameIdx);
|
||||
var methodInfo = stackFrame.GetMethod();
|
||||
return (methodInfo.ReflectedType.Name + "." + methodInfo.Name).TrimWhitespace().Trim('.');
|
||||
}
|
||||
catch (Exception) { }
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Return identity of calling method as [class.method:line].</summary>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCallerClassMethodLine()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stackFrame = new StackTrace().GetFrame(1);
|
||||
var methodInfo = stackFrame.GetMethod();
|
||||
return (methodInfo.ReflectedType.Name + "." + methodInfo.Name + ":" + stackFrame.GetFileLineNumber()).TrimWhitespace().Trim('.');
|
||||
}
|
||||
catch (Exception) { }
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Sarand.Net.Standard</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="19.0.0" />
|
||||
<PackageReference Include="MailKit" Version="2.11.1" />
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user