Browse Source

PosterEngine methods now return IPostResult objects, which contain a list of successful and failed entities.

Kenric Nugteren 1 year ago
parent
commit
d5077b970a

+ 14 - 0
InABox.Core/Postable/IPostable.cs

@@ -26,5 +26,19 @@ namespace InABox.Core
         PostedStatus PostedStatus { get; set; }
 
         string PostedNote { get; set; }
+
+        public void Post()
+        {
+            Posted = DateTime.Now;
+            PostedStatus = PostedStatus.Posted;
+            PostedNote = "";
+        }
+
+        public void FailPost(string note)
+        {
+            Posted = DateTime.Now;
+            PostedStatus = PostedStatus.PostFailed;
+            PostedNote = note;
+        }
     }
 }

+ 5 - 0
InABox.Core/Postable/IPostableFragment.cs

@@ -15,4 +15,9 @@ namespace InABox.Core
         /// </summary>
         string PostedReference { get; set; }
     }
+
+    public interface IPostableFragment<TPostable> : IPostableFragment
+        where TPostable : IPostable
+    {
+    }
 }

+ 59 - 0
InABox.Core/Postable/PostResult.cs

@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace InABox.Core
+{
+    public interface IPostResult<TPostable>
+        where TPostable : IPostable
+    {
+        /// <summary>
+        /// All successful or failed <typeparamref name="TPostable"/>s.
+        /// </summary>
+        public IEnumerable<TPostable> PostedEntities { get; }
+        public IEnumerable<TPostable> SuccessfulEntities => PostedEntities.Where(x => x.PostedStatus == PostedStatus.Posted);
+        public IEnumerable<TPostable> FailedEntities => PostedEntities.Where(x => x.PostedStatus == PostedStatus.PostFailed);
+
+        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments { get; }
+    }
+
+    public class PostResult<TPostable> : IPostResult<TPostable>
+        where TPostable : IPostable
+    {
+        private List<TPostable> posts = new List<TPostable>();
+
+        private Dictionary<Type, List<IPostableFragment<TPostable>>> fragments = new Dictionary<Type, List<IPostableFragment<TPostable>>>();
+
+        /// <summary>
+        /// All successful or failed <typeparamref name="TPostable"/>s.
+        /// </summary>
+        public IEnumerable<TPostable> PostedEntities => posts;
+
+        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments => 
+            fragments.Select(x => new KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>(x.Key, x.Value));
+
+        public void AddSuccess(TPostable post)
+        {
+            post.Post();
+            posts.Add(post);
+        }
+
+        public void AddFailed(TPostable post, string note)
+        {
+            post.FailPost(note);
+            posts.Add(post);
+        }
+
+        public void AddFragment(IPostableFragment<TPostable> fragment)
+        {
+            var type = fragment.GetType();
+            if (!fragments.TryGetValue(type, out var fragmentsList))
+            {
+                fragmentsList = new List<IPostableFragment<TPostable>>();
+                fragments[type] = fragmentsList;
+            }
+            fragmentsList.Add(fragment);
+        }
+    }
+}

+ 13 - 47
InABox.Core/Postable/PosterEngine.cs

@@ -13,7 +13,7 @@ namespace InABox.Core
     public interface IPosterEngine<TPostable>
         where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
     {
-        bool Process(IDataModel<TPostable> model);
+        IPostResult<TPostable>? Process(IDataModel<TPostable> model);
     }
 
     public interface IPosterEngine<TPostable, TPoster, TSettings> : IPosterEngine<TPostable>
@@ -55,11 +55,6 @@ namespace InABox.Core
     {
         protected TPoster Poster;
 
-        /// <summary>
-        /// A set of fragments that also need to be saved along with the <see cref="IPostable"/> entities.
-        /// </summary>
-        private Dictionary<Type, List<IPostableFragment>> Fragments = new Dictionary<Type, List<IPostableFragment>>();
-
         public PosterEngine()
         {
             Poster = CreatePoster();
@@ -74,7 +69,7 @@ namespace InABox.Core
             return poster;
         }
 
-        private TSettings _settings;
+        private TSettings? _settings;
         protected TSettings GetSettings()
         {
             _settings ??= PosterUtils.LoadPosterSettings<TPostable, TSettings>();
@@ -97,7 +92,7 @@ namespace InABox.Core
             return settings.ScriptEnabled ? settings.Script : null;
         }
 
-        protected abstract bool DoProcess(IDataModel<TPostable> model);
+        protected abstract IPostResult<TPostable> DoProcess(IDataModel<TPostable> model);
 
         /// <summary>
         /// Process the <paramref name="model"/> before loading;
@@ -111,7 +106,8 @@ namespace InABox.Core
         /// This is only called if <see cref="Process(IDataModel{TPostable})"/> returned <see langword="true"/>.
         /// </summary>
         /// <param name="model"></param>
-        public abstract void AfterPost(IDataModel<TPostable> model);
+        /// <param name="result">The result object returned by <see cref="DoProcess(IDataModel{TPostable})"/></param>
+        public abstract void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result);
 
         private static void SetFailed(IList<TPostable> entities)
         {
@@ -123,22 +119,11 @@ namespace InABox.Core
             new Client<TPostable>().Save(entities, "Post failed by user.");
         }
 
-        protected void AddFragment(IPostableFragment fragment)
-        {
-            var type = fragment.GetType();
-            if(!Fragments.TryGetValue(type, out var fragments))
-            {
-                fragments = new List<IPostableFragment>();
-                Fragments[type] = fragments;
-            }
-            fragments.Add(fragment);
-        }
-
-        public bool Process(IDataModel<TPostable> model)
+        public IPostResult<TPostable>? Process(IDataModel<TPostable> model)
         {
             if (!BeforePost(model))
             {
-                return false;
+                return null;
             }
 
             // Add posted flags; if the columns is null, then we don't need to worry, because it will be loaded.
@@ -161,35 +146,16 @@ namespace InABox.Core
 
             try
             {
-                var success = DoProcess(model);
-                if (success)
-                {
-                    AfterPost(model);
-                }
+                var result = DoProcess(model);
+                AfterPost(model, result);
 
-                var entities = data.ToObjects<TPostable>().ToList();
+                new Client<TPostable>().Save(result.PostedEntities, "Posted by user.");
 
-                if (success)
-                {
-                    foreach (var post in entities)
-                    {
-                        post.Posted = DateTime.Now;
-                        post.PostedStatus = PostedStatus.Posted;
-                        post.PostedNote = "";
-                    }
-
-                    new Client<TPostable>().Save(entities, "Posted by user.");
-
-                    foreach(var (type, fragments) in Fragments)
-                    {
-                        Client.Create(type).Save(fragments.Cast<Entity>(), "");
-                    }
-                }
-                else
+                foreach (var (type, fragments) in result.Fragments)
                 {
-                    SetFailed(entities);
+                    Client.Create(type).Save(fragments.Cast<Entity>(), "");
                 }
-                return success;
+                return result;
             }
             catch (PostCancelledException)
             {

+ 2 - 2
InABox.Core/Postable/PosterUtils.cs

@@ -164,9 +164,9 @@ namespace InABox.Core
         /// <exception cref="RepostedException">If any of the <typeparamref name="T"/> have already been processed. In this case, nothing happens.</exception>
         /// <exception cref="MissingSettingsException">If the <see cref="PostableSettings"/> for <typeparamref name="T"/> do not exist.</exception>
         /// <exception cref="PostCancelledException">If the post has been cancelled by the user.</exception>
-        /// <returns><see langword="true"/> if post was successful.</returns>
+        /// <returns><see langword="null"/> if post was unsuccessful.</returns>
         /// 
-        public static bool Process<T>(IDataModel<T> model)
+        public static IPostResult<T>? Process<T>(IDataModel<T> model)
             where T : Entity, IPostable, IRemotable, IPersistent, new()
         {
             return CreateEngine<T>().Process(model);

+ 9 - 8
InABox.Poster.CSV/CSVPosterEngine.cs

@@ -58,21 +58,21 @@ namespace InABox.Poster.CSV
             }
         }
 
-        protected override bool DoProcess(IDataModel<TPostable> model)
+        protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
         {
             var settings = GetSettings();
 
-            ICSVExport results;
+            ICSVExport<TPostable> results;
             if (GetScriptDocument() is ScriptDocument script)
             {
                 if (!script.Execute(methodname: "Process", parameters: new object[] { model }))
                 {
-                    return false;
+                    throw new Exception("Post Failed.");
                 }
 
                 var resultsObject = script.GetValue("Results");
-                results = (resultsObject as ICSVExport)
-                    ?? throw new Exception($"Script 'Results' property expected to be ICSVExport, got {resultsObject}");
+                results = (resultsObject as ICSVExport<TPostable>)
+                    ?? throw new Exception($"Script 'Results' property expected to be ICSVExport<{typeof(TPostable)}>, got {resultsObject}");
             }
             else
             {
@@ -95,7 +95,7 @@ namespace InABox.Poster.CSV
                     .Where(x => x.Name == nameof(CsvWriter.WriteRecords) && x.GetGenericArguments().Length == 1).First()
                     .MakeGenericMethod(results.Type);
                 method.Invoke(csv, new object?[] { results.Records });
-                return true;
+                return results;
             }
             else
             {
@@ -103,15 +103,16 @@ namespace InABox.Poster.CSV
             }
         }
 
-        public override void AfterPost(IDataModel<TPostable> model)
+        public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
         {
             if (GetScriptDocument() is ScriptDocument script)
             {
+                // Ignoring 'result', because this is actually the 'Results' field of the script class.
                 script.Execute(methodname: "AfterPost", parameters: new object[] { model });
             }
             else
             {
-                Poster.AfterPost(model);
+                Poster.AfterPost(model, result);
             }
         }
     }

+ 3 - 3
InABox.Poster.CSV/CSVPosterSettings.cs

@@ -29,7 +29,7 @@ using System.Collections.Generic;
 public class Module
 {
     // Output Results for CSV
-    public ICSVExport Results { get; set; }
+    public ICSVExport<" + tName + @"> Results { get; set; }
 
     // Customise 'model' before loading data. Return false if the post needs to be cancelled.
     public bool BeforePost(IDataModel<" + tName + @"> model)
@@ -41,7 +41,7 @@ public class Module
     {
         // Create new export object. You can use any object as your map,
         // but for simple cases just use " + tName + @".
-        var export = new CSVExport<" + tName + @">();
+        var export = new CSVExport<" + $"{tName}, {tName}" + @">();
 
         // Define the mapping from the fields of " + tName + @" to columns in the CSV file.
         export.DefineMapping(new()
@@ -57,7 +57,7 @@ public class Module
             // Do processing
             
             // Add item to export
-            export.Add(" + decapital + @");
+            export.Add(" + $"{decapital}, {decapital}" + @");
         }
 
         Results = export; // Set result

+ 31 - 24
InABox.Poster.CSV/ICSVPoster.cs

@@ -31,7 +31,8 @@ namespace InABox.Poster.CSV
         }
     }
 
-    public interface ICSVExport
+    public interface ICSVExport<TPostable> : IPostResult<TPostable>
+        where TPostable : IPostable
     {
         Type Type { get; }
 
@@ -40,46 +41,52 @@ namespace InABox.Poster.CSV
         IEnumerable<object> Records { get; }
     }
 
-    public interface ICSVExport<T> : ICSVExport
-        where T : class
+    public interface ICSVExport<TExport, TPostable> : ICSVExport<TPostable>
+        where TExport : class
+        where TPostable : IPostable
     {
-        new Type Type => typeof(T);
-        new CSVClassMap<T> ClassMap { get; }
-        new IEnumerable<T> Records { get; }
+        new Type Type => typeof(TExport);
+        new CSVClassMap<TExport> ClassMap { get; }
+        new IEnumerable<TExport> Records { get; }
 
-        Type ICSVExport.Type => Type;
-        ICSVClassMap ICSVExport.ClassMap => ClassMap;
-        IEnumerable<object> ICSVExport.Records => Records;
+        Type ICSVExport<TPostable>.Type => Type;
+        ICSVClassMap ICSVExport<TPostable>.ClassMap => ClassMap;
+        IEnumerable<object> ICSVExport<TPostable>.Records => Records;
     }
 
-    public class CSVExport<T> : ICSVExport<T>
-        where T : class
+    public class CSVExport<TExport, TPostable> : ICSVExport<TExport, TPostable>
+        where TExport : class
+        where TPostable : IPostable
     {
-        public CSVClassMap<T> ClassMap { get; } = new CSVClassMap<T>();
+        public CSVClassMap<TExport> ClassMap { get; } = new CSVClassMap<TExport>();
 
-        public IEnumerable<T> Records => Items;
+        public IEnumerable<TExport> Records => items.Where(x => x.Item2.PostedStatus == PostedStatus.Posted).Select(x => x.Item1);
 
-        public List<T> Items { get; } = new List<T>();
+        private List<Tuple<TExport, TPostable>> items = new List<Tuple<TExport, TPostable>>();
 
-        public void DefineMapping(List<Tuple<string, Expression<Func<T, object>>>> mappings)
+        private Dictionary<Type, List<IPostableFragment<TPostable>>> fragments = new Dictionary<Type, List<IPostableFragment<TPostable>>>();
+
+        public IEnumerable<TPostable> PostedEntities => items.Select(x => x.Item2);
+
+        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments =>
+            fragments.Select(x => new KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>(x.Key, x.Value));
+
+        public void DefineMapping(List<Tuple<string, Expression<Func<TExport, object>>>> mappings)
         {
             foreach(var (name, expr) in mappings)
             {
                 ClassMap.Map(name, expr);
             }
         }
-        public void Map(string name,  Expression<Func<T, object>> expr)
+        public void Map(string name,  Expression<Func<TExport, object>> expr)
         {
             ClassMap.Map(name, expr);
         }
 
-        public void Add(T item)
-        {
-            Items.Add(item);
-        }
-        public void AddRange(IEnumerable<T> items)
+        public void AddSuccess(TExport export, TPostable postable)
         {
-            Items.AddRange(items);
+            postable.Post();
+            items.Add(new(export, postable));
         }
     }
 
@@ -93,8 +100,8 @@ namespace InABox.Poster.CSV
     {
         bool BeforePost(IDataModel<TEntity> model);
 
-        ICSVExport Process(IDataModel<TEntity> model);
+        ICSVExport<TEntity> Process(IDataModel<TEntity> model);
 
-        void AfterPost(IDataModel<TEntity> model);
+        void AfterPost(IDataModel<TEntity> model, IPostResult<TEntity> result);
     }
 }

+ 14 - 4
InABox.Poster.Custom/CustomPosterEngine.cs

@@ -49,15 +49,25 @@ namespace InABox.Poster.Custom
             return false;
         }
 
-        protected override bool DoProcess(IDataModel<TPostable> model)
+        protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
         {
             if (GetScriptDocument() is ScriptDocument script)
             {
-                return script.Execute(methodname: "Process", parameters: new object[] { model });
+                if(!script.Execute(methodname: "Process", parameters: new object[] { model }))
+                {
+                    throw new Exception("Post Failed.");
+                }
+
+                var resultsObject = script.GetValue("Results");
+                return (resultsObject as IPostResult<TPostable>)
+                    ?? throw new Exception($"Script 'Results' property expected to be IPostResult<{typeof(TPostable)}>, got {resultsObject}");
+            }
+            else
+            {
+                throw new Exception("Post Failed.");
             }
-            return false;
         }
-        public override void AfterPost(IDataModel<TPostable> model)
+        public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
         {
             if (GetScriptDocument() is ScriptDocument script)
             {

+ 5 - 0
InABox.Poster.Custom/CustomPosterSettings.cs

@@ -23,6 +23,9 @@ using System.Collections.Generic;
 
 public class Module
 {
+    // Output Results
+    public IPostResult<" + tName + @"> Results { get; set; }
+
     // Customise 'model' before loading data. Return false if the post needs to be cancelled.
     public bool BeforePost(IDataModel<" + tName + @"> model)
     {
@@ -31,9 +34,11 @@ public class Module
 
     public bool Process(IDataModel<" + tName + @"> model)
     {
+        Results = new PostResult<" + tName + @">();
         foreach(var " + decapital + @" in model.GetTable<" + tName + @">().ToObjects<" + tName + @">())
         {
             // Do processing
+            Results.AddSuccess(decapital);
         }
 
         return true; // return true for success.

+ 2 - 6
InABox.Poster.Timberline/ITimberlinePoster.cs

@@ -13,16 +13,12 @@ namespace InABox.Poster.Timberline
         where TEntity : Entity, IPostable, IRemotable, IPersistent, new()
         where TSettings : TimberlinePosterSettings<TEntity>
     {
-        public delegate void AddFragmentCallback(IPostableFragment fragment);
-
-        event AddFragmentCallback AddFragment;
-
         ScriptDocument? Script { set; }
 
         bool BeforePost(IDataModel<TEntity> model);
 
-        bool Process(IDataModel<TEntity> model);
+        IPostResult<TEntity> Process(IDataModel<TEntity> model);
 
-        void AfterPost(IDataModel<TEntity> model);
+        void AfterPost(IDataModel<TEntity> model, IPostResult<TEntity> result);
     }
 }

+ 52 - 0
InABox.Poster.Timberline/TimberlinePostResult.cs

@@ -0,0 +1,52 @@
+using InABox.Core;
+using NPOI.SS.Formula.Functions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Timberline
+{
+    public class TimberlinePostResult<TExport, TPostable> : IPostResult<TPostable>
+        where TPostable : IPostable
+        where TExport : class
+    {
+        private List<Tuple<TPostable, TExport?>> items = new List<Tuple<TPostable, TExport?>>();
+
+        private Dictionary<Type, List<IPostableFragment<TPostable>>> fragments = new Dictionary<Type, List<IPostableFragment<TPostable>>>();
+
+        public IEnumerable<TExport> Exports => items
+            .Where(x => x.Item2 is not null && x.Item1.PostedStatus == PostedStatus.Posted)
+            .Select(x => x.Item2!);
+
+        public IEnumerable<TPostable> PostedEntities => items.Select(x => x.Item1);
+
+        public IEnumerable<Tuple<TPostable, TExport?>> Items => items;
+
+        public IEnumerable<KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>> Fragments =>
+            fragments.Select(x => new KeyValuePair<Type, IEnumerable<IPostableFragment<TPostable>>>(x.Key, x.Value));
+
+        public void AddSuccess(TPostable post, TExport export)
+        {
+            post.Post();
+            items.Add(new Tuple<TPostable, TExport?>(post, export));
+        }
+
+        public void AddFailed(TPostable post, string note)
+        {
+            post.FailPost(note);
+            items.Add(new Tuple<TPostable, TExport?>(post, null));
+        }
+
+        public void AddFragment(IPostableFragment<TPostable> fragment)
+        {
+            if (!fragments.TryGetValue(fragment.GetType(), out var fragmentList))
+            {
+                fragmentList = new List<IPostableFragment<TPostable>>();
+                fragments[fragment.GetType()] = fragmentList;
+            }
+            fragmentList.Add(fragment);
+        }
+    }
+}

+ 3 - 4
InABox.Poster.Timberline/TimberlinePosterEngine.cs

@@ -23,7 +23,6 @@ namespace InABox.Poster.Timberline
         {
             var poster = base.CreatePoster();
             poster.Script = GetScriptDocument();
-            poster.AddFragment += AddFragment;
             return poster;
         }
 
@@ -58,13 +57,13 @@ namespace InABox.Poster.Timberline
             return Poster.BeforePost(model);
         }
 
-        protected override bool DoProcess(IDataModel<TPostable> model)
+        protected override IPostResult<TPostable> DoProcess(IDataModel<TPostable> model)
         {
             return Poster.Process(model);
         }
-        public override void AfterPost(IDataModel<TPostable> model)
+        public override void AfterPost(IDataModel<TPostable> model, IPostResult<TPostable> result)
         {
-            Poster.AfterPost(model);
+            Poster.AfterPost(model, result);
         }
     }
 }