Browse Source

Added log events for importers; add import mapping factory to customise selectable import columns.
Added way to disable the import button in the import list.

Kenric Nugteren 1 year ago
parent
commit
78de32774f

+ 18 - 7
InABox.Core/Imports/BaseImporter.cs

@@ -59,6 +59,9 @@ namespace InABox.Core
 
         public event ImportSaveEvent OnSave;
 
+        public event ImportLogEvent OnLog;
+        public event ImportLogEvent OnError;
+
         public int Import()
         {
             _log.Clear();
@@ -180,7 +183,7 @@ namespace InABox.Core
                                 }
                                 catch (Exception e)
                                 {
-                                    WriteLog(iRow, string.Format("Unable to Set Value: {0} [{1}] {2}", property, value, e.Message));
+                                    WriteError(iRow, string.Format("Unable to Set Value: {0} [{1}] {2}", property, value, e.Message));
                                 }
                             }
                         }
@@ -189,7 +192,7 @@ namespace InABox.Core
                 catch (Exception e)
                 {
                     bUpdatesOK = false;
-                    WriteLog(iRow, string.Format("Unable to Update Values: {0}", e.Message));
+                    WriteError(iRow, string.Format("Unable to Update Values: {0}", e.Message));
                 }
 
                 if (bUpdatesOK)
@@ -214,7 +217,7 @@ namespace InABox.Core
                         catch (Exception e)
                         {
                             bLookupsOK = false;
-                            WriteLog(iRow, string.Format("Unable to set Primary Key: {0}", e.Message));
+                            WriteError(iRow, string.Format("Unable to set Primary Key: {0}", e.Message));
                         }
                     }
                     else
@@ -247,7 +250,7 @@ namespace InABox.Core
                                         if (mapping.Lookup == ImportLookupType.Restrict)
                                         {
                                             bLookupsOK = false;
-                                            WriteLog(iRow, string.Format("Lookup Value [{0}] not found", lookupvalue));
+                                            WriteError(iRow, string.Format("Lookup Value [{0}] not found", lookupvalue));
                                         }
                                         else if (mapping.Lookup == ImportLookupType.Create)
                                         {
@@ -279,7 +282,7 @@ namespace InABox.Core
                     catch (Exception e)
                     {
                         bLookupsOK = false;
-                        WriteLog(iRow, string.Format("Exception setting lookup values: {0}", e.Message));
+                        WriteError(iRow, string.Format("Exception setting lookup values: {0}", e.Message));
                     }
 
                     if (bLookupsOK)
@@ -312,7 +315,7 @@ namespace InABox.Core
                             }
                             catch (Exception e)
                             {
-                                WriteLog(iRow, string.Format("Unable to Save Item: {0}", e.Message));
+                                WriteError(iRow, string.Format("Unable to Save Item: {0}", e.Message));
                             }
                     }
                 }
@@ -329,7 +332,15 @@ namespace InABox.Core
 
         private void WriteLog(int row, string message)
         {
-            _log.Add(string.Format("{0:D8} {1}", row, message));
+            _log.Add($"{row:D8} {message}");
+            OnLog?.Invoke(this, $"{row:D8} {message}");
+        }
+
+        private void WriteError(int row, string message)
+        {
+            _log.Add($"{row:D8} Error: {message}");
+            OnLog?.Invoke(this, $"{row:D8} Error: {message}");
+            OnError?.Invoke(this, $"{row:D8} {message}");
         }
 
         private class ImportLookup

+ 4 - 0
InABox.Core/Imports/IImporter.cs

@@ -6,6 +6,8 @@ namespace InABox.Core
 {
     public delegate void ImportNotificationEvent(object sender, string message);
 
+    public delegate void ImportLogEvent(object sender, string message);
+
     public delegate bool ImportPreProcessEvent(object sender, Dictionary<string, string> values);
 
     public delegate bool ImportPostProcessEvent(object sender, object entity, Dictionary<string, string> values);
@@ -28,6 +30,8 @@ namespace InABox.Core
 
         IEnumerable<string> Log { get; }
         event ImportNotificationEvent OnNotify;
+        event ImportLogEvent OnError;
+        event ImportLogEvent OnLog;
 
         event ImportPreProcessEvent BeforeProcess;
         event ImportPostProcessEvent AfterProcess;

+ 46 - 5
InABox.Core/Imports/ImportFactory.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Data;
 using System.Linq;
 
 namespace InABox.Core
@@ -16,6 +17,41 @@ namespace InABox.Core
 
         public static ImportDefinition[] Definitions => _definitions.ToArray();
 
+        private static Dictionary<Type, Type>? _generators;
+        
+        private static IImportMappingGenerator? GetGenerator(Type type)
+        {
+            _generators ??= CoreUtils.TypeList(
+                    AppDomain.CurrentDomain.GetAssemblies(),
+                    myType =>
+                        myType.IsClass
+                        && !myType.IsAbstract
+                        && !myType.IsGenericType
+                        && myType.HasInterface(typeof(IImportMappingGenerator<>))
+                ).ToDictionary(x => x.GetInterfaceDefinition(typeof(IImportMappingGenerator<>))!.GenericTypeArguments[0], x => x);
+            if(_generators.TryGetValue(type, out var generatorType))
+            {
+                return Activator.CreateInstance(generatorType) as IImportMappingGenerator;
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        public static List<ImportMapping> GenerateMappings(Type t, List<ImportMapping> mappings, ImportMappingType importType)
+        {
+            var generator = GetGenerator(t);
+            if(generator is null)
+            {
+                return mappings;
+            }
+            else
+            {
+                return generator.GenerateMappings(mappings, importType);
+            }
+        }
+
         public static IEnumerable<ImportMapping> ExtractMappings(Type type, ImportMappingType Import)
         {
             var results = new List<ImportMapping>();
@@ -26,7 +62,7 @@ namespace InABox.Core
             foreach (var prop in props)
             {
 
-                if (prop.Editor != null && prop.Editor is NullEditor == false && (!prop.IsCalculated || Import == ImportMappingType.Export))
+                if (prop.Editor != null && !(prop.Editor is NullEditor) && (!prop.IsCalculated || Import == ImportMappingType.Export))
                 {
                     var bOK = true;
 
@@ -38,10 +74,15 @@ namespace InABox.Core
                         var comps = prop.Name.Split('.');
                         if (comps.Length > 1)
                         {
-                            if ((comps.Length == 2) && (prop.Editor is CodeEditor))
-                                lookup = ImportLookupType.Restrict;
-                            else
+                            if (prop.HasParentEntityLink())
+                            {
                                 bOK = false;
+                                if (prop.Editor is CodeEditor && prop.Parent!.IsEntityLink && !prop.Parent.HasParentEntityLink())
+                                {
+                                    lookup = ImportLookupType.Restrict;
+                                    bOK = true;
+                                }
+                            }
                         }
                     }
 
@@ -50,7 +91,7 @@ namespace InABox.Core
                 }
             }
 
-            return results;
+            return GenerateMappings(type, results, Import);
         }
 
         private static bool IsNestedEntityLink(Type type, string property)

+ 3 - 3
InABox.Core/Imports/ImportMapping.cs

@@ -16,7 +16,7 @@
         protected override void DoGenerateLookups()
         {
             Clear();
-            AddValue("", "");
+            //AddValue("", "");
             foreach (var field in Fields)
                 AddValue(field, field);
         }
@@ -51,10 +51,10 @@
         [CheckBoxEditor(Visible = Visible.Hidden)]
         public bool Key { get; set; }
 
-        protected override void DoPropertyChanged(string name, object before, object after)
+        protected override void DoPropertyChanged(string name, object? before, object? after)
         {
             base.DoPropertyChanged(name, before, after);
-            var bHasValue = false;
+            bool bHasValue;
             if (name.Equals("Field"))
             {
                 var constant = after as string;

+ 15 - 0
InABox.Core/Imports/ImportMappingGenerator.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace InABox.Core
+{
+    public interface IImportMappingGenerator
+    {
+        public List<ImportMapping> GenerateMappings(List<ImportMapping> mappings, ImportMappingType importType);
+    }
+
+    public interface IImportMappingGenerator<T> : IImportMappingGenerator
+    {
+    }
+}

+ 5 - 0
inabox.wpf/DynamicGrid/DynamicImportForm.xaml.cs

@@ -4,6 +4,7 @@ using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Forms.Design;
 using InABox.Core;
 using InABox.Wpf;
 using Microsoft.Win32;
@@ -77,6 +78,8 @@ namespace InABox.DynamicGrid
                 }
             }
 
+            ImportFieldGenerator.Fields = Array.Empty<string>();
+
             if (File.Exists(FileName.Text) && Type.SelectedValue as ImportDefinition != null)
                 OpenFile(FileName.Text);
             else
@@ -170,6 +173,8 @@ namespace InABox.DynamicGrid
             ColumnWidths.Visibility = _importdefinition != null && _importdefinition.Type == typeof(FixedWidthImporter<>)
                 ? Visibility.Visible
                 : Visibility.Hidden;
+            if (File.Exists(FileName.Text) && Type.SelectedValue as ImportDefinition != null)
+                OpenFile(FileName.Text);
             CheckOKButton();
         }
 

+ 161 - 109
inabox.wpf/DynamicGrid/DynamicImportGrid.cs

@@ -1,13 +1,16 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Windows;
+using System.Windows.Forms.Design;
 using System.Windows.Media.Imaging;
 using InABox.Core;
 using InABox.Scripting;
 using InABox.WPF;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Microsoft.Win32;
 
 namespace InABox.DynamicGrid
@@ -25,10 +28,29 @@ namespace InABox.DynamicGrid
     {
         private static readonly BitmapImage run = Wpf.Resources.rightarrow.AsBitmapImage();
 
+        private DynamicImageColumn ImportColumn;
+
+        private bool _canImport = true;
+        public bool CanImport
+        {
+            get => _canImport;
+            set
+            {
+                if(_canImport != value)
+                {
+                    _canImport = value;
+                    ImportColumn.Position = _canImport ? DynamicActionColumnPosition.End : DynamicActionColumnPosition.Hidden;
+                    Refresh(true, false);
+                }
+            }
+        }
+
         protected override void Init()
         {
             base.Init();
-            ActionColumns.Add(new DynamicImageColumn(ImportImage, ImportAction));
+            ImportColumn = new DynamicImageColumn(ImportImage, ImportAction);
+            ActionColumns.Add(ImportColumn);
+
             HiddenColumns.Add(x => x.EntityName);
             HiddenColumns.Add(x => x.ImporterDescription);
             HiddenColumns.Add(x => x.FileName);
@@ -68,14 +90,14 @@ namespace InABox.DynamicGrid
             return arg != null ? run : null;
         }
 
-        private int[] ExtractColumnWidths(string widths)
+        private static int[] ExtractColumnWidths(string widths)
         {
             if (string.IsNullOrWhiteSpace(widths))
                 return new[] { 1024 };
 
             try
             {
-                return widths.Split(',').Select(x => int.Parse(x)).ToArray();
+                return widths.Split(',').Select(int.Parse).ToArray();
             }
             catch
             {
@@ -83,135 +105,165 @@ namespace InABox.DynamicGrid
             }
         }
 
-        private bool ImportAction(CoreRow? arg)
+        public static bool CreateImporter(Importer importer,
+            [NotNullWhen(true)]
+            ref string? filename,
+            [NotNullWhen(true)]
+            out IImporter iimporter,
+            Action<CustomiseImportArgs>? customiseImport = null,
+            Func<object, bool>? onImport = null)
         {
-            if (arg != null)
+            iimporter = null;
+            var definition =
+                ImportFactory.Definitions.FirstOrDefault(x => x.Description.Equals(importer.ImporterDescription));
+            var entityType = CoreUtils.GetEntity(importer.EntityName);
+            if (definition != null)
             {
-                var definition =
-                    ImportFactory.Definitions.FirstOrDefault(x => x.Description.Equals(arg.Get<Importer, string>(c => c.ImporterDescription)));
-                if (definition != null)
+                ScriptDocument? helper = null;
+                try
                 {
-                    ScriptDocument? helper = null;
-                    try
+                    if (!importer.Script.IsNullOrWhiteSpace())
                     {
-                        var script = arg.Get<Importer, string>(x => x.Script);
-                        if (!string.IsNullOrWhiteSpace(script))
+                        helper = new ScriptDocument(importer.Script);
+                        if (!helper.Compile())
                         {
-                            helper = new ScriptDocument(script);
-                            if (!helper.Compile())
-                            {
-                                MessageBox.Show("Unable to Compile Import Helper Script!");
-                                return false;
-                            }
+                            MessageBox.Show("Unable to Compile Import Helper Script!");
+                            return false;
                         }
                     }
-                    catch (Exception e)
-                    {
-                        Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                    }
+                }
+                catch (Exception e)
+                {
+                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
+                }
 
-                    var fullfilename = arg.Get<Importer, string>(x => x.FileName);
-                    var path = Path.GetDirectoryName(fullfilename);
-                    var extension = Path.GetExtension(fullfilename);
-                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(fullfilename) };
-                    OnCustomiseImport?.Invoke(this, args);
-                    var filename = CoreUtils.SanitiseFileName(args.FileName);
-                    var dlg = new OpenFileDialog();
-                    dlg.Filter = definition.Filter;
-                    dlg.FileName = Path.Combine(path, filename) + (extension.StartsWith(".") ? "" : ".") + extension;
+                if(filename is null)
+                {
+                    var fullFileName = importer.FileName;
+                    var path = Path.GetDirectoryName(fullFileName);
+                    var extension = Path.GetExtension(fullFileName);
+                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(fullFileName) };
+                    customiseImport?.Invoke(args);
+
+                    var filename2 = CoreUtils.SanitiseFileName(args.FileName);
+                    var dlg = new OpenFileDialog
+                    {
+                        Filter = definition.Filter,
+                        FileName = Path.Combine(path, filename2) + (extension.StartsWith(".") ? "" : ".") + extension
+                    };
                     if (!string.IsNullOrWhiteSpace(dlg.FileName) && Directory.Exists(Path.GetDirectoryName(dlg.FileName)))
                         dlg.InitialDirectory = Path.GetDirectoryName(dlg.FileName);
-                    if (dlg.ShowDialog() == true)
+                    if(dlg.ShowDialog() == true)
                     {
-                        Progress.Show("Importing Data");
-                        var importer = ImportFactory.Create(definition, EntityType);
-                        if (importer is IFixedWidthImporter)
-                            ((IFixedWidthImporter)importer).ColumnWidths = ExtractColumnWidths(arg.Get<Importer, string>(x => x.ColumnWidths));
+                        filename = dlg.FileName;
+                    }
+                    else
+                    {
+                        return false;
+                    }
+                }
+                else
+                {
+                    var path = Path.GetDirectoryName(filename);
+                    var extension = Path.GetExtension(filename);
+                    var args = new CustomiseImportArgs { FileName = Path.GetFileNameWithoutExtension(filename) };
+                    customiseImport?.Invoke(args);
 
-                        try
-                        {
-                            importer.HasHeader = arg.Get<Importer, bool>(x => x.HasHeader);
-                        }
-                        catch
-                        {
-                            importer.HasHeader = true;
-                        }
+                    var filename2 = CoreUtils.SanitiseFileName(args.FileName);
+                    filename = Path.ChangeExtension(Path.Combine(path, filename2), extension);
+                }
 
-                        try
-                        {
-                            importer.HeaderRow = Math.Max(1, arg.Get<Importer, int>(x => x.HeaderRows));
-                        }
-                        catch
-                        {
-                            importer.HeaderRow = 1;
-                        }
+                iimporter = ImportFactory.Create(definition, entityType);
+                if (iimporter is IFixedWidthImporter fixedImporter)
+                    fixedImporter.ColumnWidths = ExtractColumnWidths(importer.ColumnWidths);
 
-                        importer.BeforeProcess += (sender, values) =>
-                        {
-                            if (helper != null)
-                                return helper.Execute("Module", "BeforeProcess", new object[] { values });
-                            return true;
-                        };
+                iimporter.HasHeader = importer.HasHeader;
+                iimporter.HeaderRow = Math.Max(1, importer.HeaderRows);
 
-                        importer.AfterProcess += (sender, item, values) =>
-                        {
-                            var bOK = true;
-                            if (helper != null)
-                                bOK = helper.Execute("Module", "AfterProcess", new[] { item, values });
-                            bOK = !bOK || OnImportItem == null || OnImportItem.Invoke(item);
-                            return bOK;
-                        };
-
-                        importer.OnSave += (sender, entity) => OnSave?.Invoke(sender, entity);
-                        importer.OnLoad += (sender, type, fields, id) => OnLoad(sender, type, fields, id); 
-                        
-                        importer.OnNotify += (o, m) => { Progress.SetMessage(m); };
-
-                        var settings = arg.Get<Importer, string>(c => c.Definition);
-                        var mappings = Serialization.Deserialize<List<ImportMapping>>(settings);
-                        importer.Mappings.AddRange(mappings);
-                        using (var stream = new FileStream(dlg.FileName, FileMode.Open, FileAccess.Read))
+                iimporter.BeforeProcess += (sender, values) =>
+                {
+                    if (helper != null)
+                        return helper.Execute("Module", "BeforeProcess", new object[] { values });
+                    return true;
+                };
+
+                iimporter.AfterProcess += (sender, item, values) =>
+                {
+                    var bOK = true;
+                    if (helper != null)
+                        bOK = helper.Execute("Module", "AfterProcess", new[] { item, values });
+                    bOK = !bOK || onImport is null || onImport.Invoke(item);
+                    return bOK;
+                };
+
+                var settings = importer.Definition;
+                var mappings = Serialization.Deserialize<List<ImportMapping>>(settings);
+                iimporter.Mappings.AddRange(mappings);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        private bool ImportAction(CoreRow? arg)
+        {
+            if (arg != null)
+            {
+                var importer = arg.ToObject<Importer>();
+                string? filename = null;
+
+                if (CreateImporter(importer,
+                    ref filename,
+                    out var iimporter,
+                    (args) => OnCustomiseImport?.Invoke(this, args),
+                    (o) => OnImportItem?.Invoke(o) != false))
+                {
+                    iimporter.OnLoad += OnLoad;
+                    iimporter.OnSave += OnSave;
+
+                    Progress.Show("Importing Data");
+
+                    using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
+                    Progress.SetMessage("Opening File");
+                    if (iimporter.Open(stream))
+                    {
+                        if (iimporter.ReadHeader())
                         {
-                            Progress.SetMessage("Opening File");
-                            if (importer.Open(stream))
+                            var mismatches = iimporter.Mappings.Where(x =>
+                                !string.IsNullOrWhiteSpace(x.Field) &&
+                                !iimporter.Fields.Contains(x.Field)
+                            ).Select(x => x.Field).ToArray();
+                            if (!mismatches.Any())
                             {
-                                if (importer.ReadHeader())
-                                {
-                                    var mismatches = mappings.Where(x =>
-                                        !string.IsNullOrWhiteSpace(x.Field) &&
-                                        !importer.Fields.Contains(x.Field)
-                                    ).Select(x => x.Field).ToArray();
-                                    if (!mismatches.Any())
-                                    {
-                                        var imported = importer.Import();
-                                        Progress.Close();
-                                        MessageBox.Show(string.Format("Imported {0} records!", imported));
-                                        var LogFile = Path.ChangeExtension(dlg.FileName, ".log");
-                                        File.AppendAllLines(LogFile, importer.Log.ToArray());
-                                        Process.Start(new ProcessStartInfo(LogFile) { UseShellExecute = true });
-                                    }
-                                    else
-                                    {
-                                        Progress.Close();
-                                        MessageBox.Show("Import Mappings do not match file headers!\n\n- " + string.Join("\n- ", mismatches),
-                                            "Import Failed");
-                                    }
-                                }
-                                else
-                                {
-                                    Progress.Close();
-                                    MessageBox.Show("Unable to Read Headers from {0}", Path.GetFileName(dlg.FileName));
-                                }
+                                var imported = iimporter.Import();
+                                Progress.Close();
+                                MessageBox.Show(string.Format("Imported {0} records!", imported));
+                                var LogFile = Path.ChangeExtension(filename, ".log");
+                                File.AppendAllLines(LogFile, iimporter.Log.ToArray());
+                                Process.Start(new ProcessStartInfo(LogFile) { UseShellExecute = true });
                             }
                             else
                             {
                                 Progress.Close();
-                                MessageBox.Show("Unable to Open {0}", Path.GetFileName(dlg.FileName));
+                                MessageBox.Show("Import Mappings do not match file headers!\n\n- " + string.Join("\n- ", mismatches),
+                                    "Import Failed");
                             }
-
-                            importer.Close();
+                        }
+                        else
+                        {
+                            Progress.Close();
+                            MessageBox.Show("Unable to Read Headers from {0}", Path.GetFileName(filename));
                         }
                     }
+                    else
+                    {
+                        Progress.Close();
+                        MessageBox.Show("Unable to Open {0}", Path.GetFileName(filename));
+                    }
+
+                    iimporter.Close();
                 }
             }
 
@@ -243,7 +295,7 @@ namespace InABox.DynamicGrid
             return result;
         }
 
-        public override bool EditItems(Importer[] items, Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false)
+        public override bool EditItems(Importer[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
         {
             if (items.Length != 1)
             {

+ 2 - 1
inabox.wpf/DynamicGrid/DynamicImportList.xaml.cs

@@ -10,7 +10,7 @@ namespace InABox.DynamicGrid
     /// </summary>
     public partial class DynamicImportList : ThemableWindow
     {
-        public DynamicImportList(Type entitytype, Guid entityid)
+        public DynamicImportList(Type entitytype, Guid entityid, bool canImport = true)
         {
             InitializeComponent();
             Imports.EntityType = entitytype;
@@ -19,6 +19,7 @@ namespace InABox.DynamicGrid
             Imports.OnCustomiseImport += (o, e) => { OnCustomiseImport?.Invoke(o, e); };
             Imports.OnSave += (sender, entity) => OnSave?.Invoke(sender, entity);
             Imports.OnLoad += (sender, type, fields, id) => OnLoad(sender, type, fields, id);
+            Imports.CanImport = canImport;
             Imports.Refresh(true, true);
         }
 

+ 14 - 2
inabox.wpf/DynamicGrid/DynamicImportMappingGrid.cs

@@ -158,10 +158,22 @@ namespace InABox.DynamicGrid
                 return false;
             var item = Items[arg.Index];
             if (string.IsNullOrWhiteSpace(item.Field) && string.IsNullOrWhiteSpace(item.Constant))
-                item.Key = false;
+            {
+                if (item.Key)
+                {
+                    item.Key = false;
+                    return true;
+                }
+                else
+                {
+                    return false;
+                }
+            }
             else
+            {
                 item.Key = !item.Key;
-            return true;
+                return true;
+            }
         }
 
         private BitmapImage? LookupImage(CoreRow? arg)