Parcourir la source

Merge remote-tracking branch 'origin/kenric' into frank

frogsoftware il y a 1 an
Parent
commit
8cfd498210

+ 1 - 1
InABox.Client.RPC/RPCClient.cs

@@ -115,7 +115,7 @@ namespace InABox.Rpc
             var parameters = new RpcCheck2FAParameters()
             {
                 Code = code,
-                SessionId = session ?? Guid.Empty,
+                SessionId = session ?? ClientFactory.SessionID,
             };
             var result = _transport.Send<RpcCheck2FACommand, RpcCheck2FAParameters, RpcCheck2FAResult>(parameters);
             return result.Valid;

+ 1 - 1
InABox.Client.RPC/Transports/RPCClientTransport.cs

@@ -92,7 +92,7 @@ namespace InABox.Rpc
             var response = GetResponse(request.Id, ev, DefaultRequestTimeout)
                 ?? throw new Exception($"{typeof(TCommand).Name}({request.Id}) returned NULL");
 
-            if (response.Error != RpcError.NONE)throw new RpcException($"Server error in {typeof(TCommand).Name}({request.Id})", response.Error);
+            if (response.Error != RpcError.NONE) throw new RpcException($"Server error in {typeof(TCommand).Name}({request.Id}): {response.Error}", response.Error);
             
             var result = Serialization.ReadBinary<TResult>(response.Payload, BinarySerializationSettings.Latest)
                 ?? throw new Exception($"Cannot Deserialize {typeof(TCommand).Name}({request.Id})");

+ 21 - 4
InABox.Core/DataModel/DataModel.cs

@@ -83,6 +83,7 @@ namespace InABox.Core
         void AddTable(string alias, CoreTable table, bool isdefault = false);
 
         void AddTable(Type type, CoreTable table, bool isdefault = false, string? alias = null);
+
         /// <summary>
         /// Adds a table to the datamodel.
         /// </summary>
@@ -156,12 +157,22 @@ namespace InABox.Core
         bool HasTable(Type type, string? alias = null);
         bool HasTable<TType>(string? alias = null);
 
-        void LoadModel(IEnumerable<string> requiredTables, Dictionary<string, IQueryDef>? requiredQueries = null);
-        void LoadModel(IEnumerable<string> requiredTables, params IDataModelQueryDef[] requiredQueries);
+        void LoadModel(IEnumerable<string>? requiredTables, Dictionary<string, IQueryDef>? requiredQueries = null);
+        void LoadModel(IEnumerable<string>? requiredTables, params IDataModelQueryDef[] requiredQueries);
+
+        /// <summary>
+        /// Load the model, loading all tables that are set to be default. (See <see cref="SetIsDefault{TType}(bool, string?)"/>).
+        /// </summary>
+        void LoadModel();
 
         TType[] ExtractValues<TSource, TType>(Expression<Func<TSource, TType>> column, bool distinct = true, string? alias = null);
     }
 
+    public interface IDataModel<T> : IDataModel
+        where T : Entity, IRemotable, IPersistent, new()
+    {
+    }
+
     public abstract class DataModel : IDataModel
     {
         private readonly List<IDataModelRelationship> _relationships = new List<IDataModelRelationship>();
@@ -465,11 +476,16 @@ namespace InABox.Core
             if (!args.Cancel) AfterLoad(requiredTablesList);
         }
 
-        public void LoadModel(IEnumerable<string> requiredTables, params IDataModelQueryDef[] requiredQueries)
+        public void LoadModel(IEnumerable<string>? requiredTables, params IDataModelQueryDef[] requiredQueries)
         {
             LoadModel(requiredTables, requiredQueries.ToDictionary(x => x.TableName, x => x as IQueryDef));
         }
 
+        public void LoadModel()
+        {
+            LoadModel(DefaultTableNames);
+        }
+
         #endregion
 
         #region Non-Generic Stuff
@@ -731,7 +747,8 @@ namespace InABox.Core
         #endregion
     }
 
-    public abstract class DataModel<T> : DataModel where T : Entity, IRemotable, IPersistent, new()
+    public abstract class DataModel<T> : DataModel, IDataModel<T>
+        where T : Entity, IRemotable, IPersistent, new()
     {
         public DataModel(Filter<T> filter, Columns<T>? columns = null, SortOrder<T>? sort = null)
         {

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

@@ -8,6 +8,7 @@ namespace InABox.Core
     {
         NeverPosted,
         PostFailed,
+        RequiresRepost,
         Posted
     }
 
@@ -23,5 +24,7 @@ namespace InABox.Core
         DateTime Posted { get; set; }
 
         PostedStatus PostedStatus { get; set; }
+
+        string PostedNote { get; set; }
     }
 }

+ 1 - 1
InABox.Core/Postable/IPoster.cs

@@ -13,7 +13,7 @@ namespace InABox.Core
     /// <typeparam name="TEntity">The type of entity that this poster can process.</typeparam>
     /// <typeparam name="TSettings">The <see cref="PosterSettings"/> specific to this type of poster.</typeparam>
     public interface IPoster<TEntity, TSettings>
-        where TEntity : Entity, IPostable
+        where TEntity : Entity, IPostable, IRemotable, IPersistent, new()
         where TSettings : PosterSettings
     {
     }

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

@@ -6,6 +6,11 @@ namespace InABox.Core
 {
     namespace Postable
     {
+        public class EmptyPostException : Exception
+        {
+            public EmptyPostException() { }
+        }
+
         public class MissingSettingsException : Exception
         {
             public Type PostableType { get; }

+ 81 - 22
InABox.Core/Postable/PosterEngine.cs

@@ -4,6 +4,7 @@ using InABox.Core.Postable;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 
 namespace InABox.Core
@@ -12,7 +13,7 @@ namespace InABox.Core
     public interface IPosterEngine<TPostable>
         where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
     {
-        bool Process(IEnumerable<TPostable> posts);
+        bool Process(IDataModel<TPostable> model);
     }
 
     public interface IPosterEngine<TPostable, TPoster, TSettings> : IPosterEngine<TPostable>
@@ -22,16 +23,11 @@ namespace InABox.Core
     {
     }
 
-    public abstract class PosterEngine<TPostable, TPoster, TSettings> : IPosterEngine<TPostable, TPoster, TSettings>
-        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
-        where TPoster : IPoster<TPostable, TSettings>
-        where TSettings : PosterSettings, new()
+    internal static class PosterEngineUtils
     {
-        protected static TPoster Poster = GetPoster();
-
         private static Type[]? _posters;
 
-        private static TPoster GetPoster()
+        public static Type GetPoster(Type TPoster)
         {
             _posters ??= CoreUtils.TypeList(
                 AppDomain.CurrentDomain.GetAssemblies(),
@@ -40,10 +36,35 @@ namespace InABox.Core
                     && !x.IsGenericType
                     && x.HasInterface(typeof(IPoster<,>))
             ).ToArray();
+            return _posters.Where(x => TPoster.IsAssignableFrom(x)).FirstOrDefault()
+                ?? throw new Exception($"No poster of type {TPoster}.");
+        }
+    }
+
+    /// <summary>
+    /// A base class for all <see cref="IPosterEngine{TPostable}"/>. A concrete instance of this will be loaded by
+    /// <see cref="PosterUtils.Process{T}(IDataModel{T})"/>; a new instance is guaranteed to be created each time that method is called.
+    /// </summary>
+    /// <typeparam name="TPostable"></typeparam>
+    /// <typeparam name="TPoster"></typeparam>
+    /// <typeparam name="TSettings"></typeparam>
+    public abstract class PosterEngine<TPostable, TPoster, TSettings> : IPosterEngine<TPostable, TPoster, TSettings>
+        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
+        where TPoster : class, IPoster<TPostable, TSettings>
+        where TSettings : PosterSettings, new()
+    {
+        protected TPoster Poster;
 
-            var type = _posters.Where(x => typeof(TPoster).IsAssignableFrom(x)).FirstOrDefault()
-                ?? throw new Exception($"No poster of type {typeof(TPoster)}.");
-            return (TPoster)Activator.CreateInstance(type);
+        public PosterEngine()
+        {
+            Poster = CreatePoster();
+        }
+
+        private static readonly Type? PosterType = PosterEngineUtils.GetPoster(typeof(TPoster));
+
+        protected virtual TPoster CreatePoster()
+        {
+            return (Activator.CreateInstance(PosterType!) as TPoster)!;
         }
 
         protected static TSettings GetSettings()
@@ -66,45 +87,83 @@ namespace InABox.Core
             return settings.ScriptEnabled ? settings.Script : null;
         }
 
-        protected abstract bool DoProcess(IEnumerable<TPostable> posts);
+        protected abstract bool DoProcess(IDataModel<TPostable> model);
+
+        /// <summary>
+        /// Process the <paramref name="model"/> before loading;
+        /// </summary>
+        /// <param name="model"></param>
+        /// <returns><see langword="false"/> if the processing must be cancelled.</returns>
+        public abstract bool BeforePost(IDataModel<TPostable> model);
+
+        /// <summary>
+        /// Prior to saving the <typeparamref name="TPostable"/> entities, make any necessary changes to those entities.
+        /// 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);
 
-        public bool Process(IEnumerable<TPostable> posts)
+        public bool Process(IDataModel<TPostable> model)
         {
-            var list = posts.AsList();
-            if(list.Any(x => x.PostedStatus == PostedStatus.Posted))
+            if (!BeforePost(model))
+            {
+                return false;
+            }
+            model.LoadModel();
+
+            var data = model.GetTable<TPostable>();
+
+            if (!data.Rows.Any())
+            {
+                throw new EmptyPostException();
+            }
+            if(data.Rows.Any(x => x.Get<TPostable, PostedStatus>(x => x.PostedStatus) == PostedStatus.Posted))
             {
                 throw new RepostedException();
             }
+
             try
             {
-                var success = DoProcess(list);
+                var success = DoProcess(model);
                 if (success)
                 {
-                    foreach (var post in list)
+                    AfterPost(model);
+                }
+
+                var entities = data.ToObjects<TPostable>().ToList();
+
+                if (success)
+                {
+                    foreach (var post in entities)
                     {
                         post.Posted = DateTime.Now;
                         post.PostedStatus = PostedStatus.Posted;
+                        post.PostedNote = "";
                     }
-                    new Client<TPostable>().Save(list, "Posted by user.");
+                    new Client<TPostable>().Save(entities, "Posted by user.");
                 }
                 else
                 {
-                    foreach (var post in list)
+                    foreach (var post in entities)
                     {
                         post.PostedStatus = PostedStatus.PostFailed;
+                        post.PostedNote = "Post failed.";
                     }
-                    new Client<TPostable>().Save(list, "Post failed by user.");
+                    new Client<TPostable>().Save(entities, "Post failed by user.");
                 }
                 return success;
             }
             catch(Exception e)
             {
                 Logger.Send(LogType.Error, "", $"Post Failed: {CoreUtils.FormatException(e)}");
-                foreach (var post in list)
+
+                var entities = data.ToObjects<TPostable>().ToList();
+                foreach (var post in entities)
                 {
                     post.PostedStatus = PostedStatus.PostFailed;
+                    post.PostedNote = e.Message;
                 }
-                new Client<TPostable>().Save(list, "Post failed by user.");
+                new Client<TPostable>().Save(entities, "Post failed by user.");
                 throw;
             }
         }

+ 7 - 6
InABox.Core/Postable/PosterUtils.cs

@@ -140,20 +140,21 @@ namespace InABox.Core
         }
 
         /// <summary>
-        /// Process <paramref name="entities"/> with the currently set <see cref="IPosterEngine{TPostable, TPoster, TSettings}"/>
+        /// Process <paramref name="model"/> with the currently set <see cref="IPosterEngine{TPostable, TPoster, TSettings}"/>
         /// for <typeparamref name="T"/>.
         /// </summary>
-        /// <typeparam name="T">The type of <paramref name="entities"/> that needs to be processed.</typeparam>
-        /// <param name="entities"></param>
-        /// <exception cref="RepostedException">If any of the <paramref name="entities"/> has already been processed. In this case, nothing happens.</exception>
+        /// <typeparam name="T">The type of <paramref name="model"/> that needs to be processed.</typeparam>
+        /// <param name="model"></param>
+        /// <exception cref="EmptyPostException">If there are no items to post. In this case, nothing happens.</exception>
+        /// <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>
         /// 
-        public static bool Process<T>(IEnumerable<T> entities)
+        public static bool Process<T>(IDataModel<T> model)
             where T : Entity, IPostable, IRemotable, IPersistent, new()
         {
-            return CreateEngine<T>().Process(entities);
+            return CreateEngine<T>().Process(model);
         }
     }
 }

+ 55 - 7
InABox.Poster.CSV/CSVPosterEngine.cs

@@ -16,12 +16,18 @@ namespace InABox.Poster.CSV
     public class CSVPosterEngine<TPostable> : PosterEngine<TPostable, ICSVPoster<TPostable>, CSVPosterSettings>
         where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
     {
-        protected override bool DoProcess(IEnumerable<TPostable> posts)
+        private ScriptDocument? _script;
+        private bool _hasCheckedScript;
+
+        private ScriptDocument? GetScriptDocument()
         {
-            var settings = GetSettings();
+            if (_hasCheckedScript)
+            {
+                return _script;
+            }
 
-            ICSVExport results;
-            if(settings.ScriptEnabled && !string.IsNullOrWhiteSpace(settings.Script))
+            var settings = GetSettings();
+            if (settings.ScriptEnabled && !string.IsNullOrWhiteSpace(settings.Script))
             {
                 var document = new ScriptDocument(settings.Script);
                 document.Properties.Add(new ScriptProperty("Results", null));
@@ -29,18 +35,48 @@ namespace InABox.Poster.CSV
                 {
                     throw new Exception("Script failed to compile!");
                 }
-                if(!document.Execute(methodname: "Process", parameters: new object[] { posts }))
+                _script = document;
+            }
+            else
+            {
+                _script = null;
+            }
+
+            _hasCheckedScript = true;
+            return _script;
+        }
+
+        public override bool BeforePost(IDataModel<TPostable> model)
+        {
+            if(GetScriptDocument() is ScriptDocument script)
+            {
+                return script.Execute(methodname: "BeforePost", parameters: new object[] { model });
+            }
+            else
+            {
+                return Poster.BeforePost(model);
+            }
+        }
+
+        protected override bool DoProcess(IDataModel<TPostable> model)
+        {
+            var settings = GetSettings();
+
+            ICSVExport results;
+            if (GetScriptDocument() is ScriptDocument script)
+            {
+                if (!script.Execute(methodname: "Process", parameters: new object[] { model }))
                 {
                     return false;
                 }
 
-                var resultsObject = document.GetValue("Results");
+                var resultsObject = script.GetValue("Results");
                 results = (resultsObject as ICSVExport)
                     ?? throw new Exception($"Script 'Results' property expected to be ICSVExport, got {resultsObject}");
             }
             else
             {
-                results = Poster.Process(posts);
+                results = Poster.Process(model);
             }
 
             var dlg = new SaveFileDialog()
@@ -66,5 +102,17 @@ namespace InABox.Poster.CSV
                 throw new PostCancelledException();
             }
         }
+
+        public override void AfterPost(IDataModel<TPostable> model)
+        {
+            if (GetScriptDocument() is ScriptDocument script)
+            {
+                script.Execute(methodname: "AfterPost", parameters: new object[] { model });
+            }
+            else
+            {
+                Poster.AfterPost(model);
+            }
+        }
     }
 }

+ 20 - 6
InABox.Poster.CSV/CSVPosterSettings.cs

@@ -23,6 +23,7 @@ namespace InABox.Poster.CSV
             return @"
 using " + ns + @";
 using InABox.Poster.CSV;
+using InABox.Core;
 using System.Collections.Generic;
 
 public class Module
@@ -30,30 +31,43 @@ public class Module
     // Output Results for CSV
     public ICSVExport Results { get; set; }
 
-    public bool Process(IEnumerable<" + tName + @"> items)
+    // Customise 'model' before loading data. Return false if the post needs to be cancelled.
+    public bool BeforePost(IDataModel<" + tName + @"> model)
+    {
+        return true;
+    }
+
+    public bool Process(IDataModel<" + tName + @"> model)
     {
         // Create new export object. You can use any object as your map,
-        // but for simple cases just use " + tName +  @"
+        // but for simple cases just use " + tName + @".
         var export = new CSVExport<" + tName + @">();
 
         // Define the mapping from the fields of " + tName + @" to columns in the CSV file.
         export.DefineMapping(new()
         {
-            new(""ID"", " + decapital + @" => " + decapital +  @".ID),
+            new(""ID"", " + decapital + @" => " + decapital + @".ID),
             new(""Column1"", " + decapital + @" => " + decapital +  @".Column1),
             new(""Column2"", " + decapital + @" => " + decapital +  @".Column2),
-            new(""Column3"", " + decapital + @" => " + decapital + @".Column3)
+            new(""Column3"", " + decapital + @" => " + decapital + @".Column3),
             // etc.
         });
-        foreach(var item in items)
+        foreach(var " + decapital + @" in model.GetTable<" + tName + @">().ToObjects<" + tName + @">())
         {
             // Do processing
-            export.Add(item);
+            
+            // Add item to export
+            export.Add(" + decapital + @");
         }
 
         Results = export; // Set result
         return true; // return true for success.
     }
+
+    // Perform any post-processing. All the items of type '" + tName + @" will be saved after this function is called.
+    public void AfterPost(IDataModel<" + tName + @"> model)
+    {
+    }
 }";
         }
     }

+ 6 - 2
InABox.Poster.CSV/ICSVPoster.cs

@@ -89,8 +89,12 @@ namespace InABox.Poster.CSV
     /// <typeparam name="TEntity"></typeparam>
     [Caption("CSV")]
     public interface ICSVPoster<TEntity> : IPoster<TEntity, CSVPosterSettings>
-        where TEntity : Entity, IPostable
+        where TEntity : Entity, IPostable, IRemotable, IPersistent, new()
     {
-        ICSVExport Process(IEnumerable<TEntity> entities);
+        bool BeforePost(IDataModel<TEntity> model);
+
+        ICSVExport Process(IDataModel<TEntity> model);
+
+        void AfterPost(IDataModel<TEntity> model);
     }
 }

+ 68 - 0
InABox.Poster.Custom/CustomPosterEngine.cs

@@ -0,0 +1,68 @@
+using InABox.Core;
+using InABox.Scripting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Custom
+{
+    public class CustomPosterEngine<TPostable> : PosterEngine<TPostable, ICustomPoster<TPostable>, CustomPosterSettings>
+        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
+    {
+        private ScriptDocument? _script;
+        private bool _hasCheckedScript;
+
+        private ScriptDocument? GetScriptDocument()
+        {
+            if (_hasCheckedScript)
+            {
+                return _script;
+            }
+
+            var settings = GetSettings();
+            if (settings.ScriptEnabled && !string.IsNullOrWhiteSpace(settings.Script))
+            {
+                var document = new ScriptDocument(settings.Script);
+                if (!document.Compile())
+                {
+                    throw new Exception("Script failed to compile!");
+                }
+                _script = document;
+            }
+            else
+            {
+                _script = null;
+            }
+
+            _hasCheckedScript = true;
+            return _script;
+        }
+
+        public override bool BeforePost(IDataModel<TPostable> model)
+        {
+            if (GetScriptDocument() is ScriptDocument script)
+            {
+                return script.Execute(methodname: "BeforePost", parameters: new object[] { model });
+            }
+            return false;
+        }
+
+        protected override bool DoProcess(IDataModel<TPostable> model)
+        {
+            if (GetScriptDocument() is ScriptDocument script)
+            {
+                return script.Execute(methodname: "Process", parameters: new object[] { model });
+            }
+            return false;
+        }
+        public override void AfterPost(IDataModel<TPostable> model)
+        {
+            if (GetScriptDocument() is ScriptDocument script)
+            {
+                script.Execute(methodname: "AfterPost", parameters: new object[] { model });
+            }
+        }
+    }
+}

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

@@ -0,0 +1,49 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Custom
+{
+    public class CustomPosterSettings : PosterSettings
+    {
+        public override string DefaultScript(Type TPostable)
+        {
+            var tName = TPostable.Name;
+            var decapital = tName[0..1].ToLower() + tName[1..];
+
+            var ns = TPostable.Namespace;
+
+            return @"
+using " + ns + @";
+using InABox.Core;
+using System.Collections.Generic;
+
+public class Module
+{
+    // Customise 'model' before loading data. Return false if the post needs to be cancelled.
+    public bool BeforePost(IDataModel<" + tName + @"> model)
+    {
+        return true;
+    }
+
+    public bool Process(IDataModel<" + tName + @"> model)
+    {
+        foreach(var " + decapital + @" in model.GetTable<" + tName + @">().ToObjects<" + tName + @">())
+        {
+            // Do processing
+        }
+
+        return true; // return true for success.
+    }
+
+    // Perform any post-processing. All the items of type '" + tName + @" will be saved after this function is called.
+    public void AfterPost(IDataModel<" + tName + @"> model)
+    {
+    }
+}";
+        }
+    }
+}

+ 10 - 0
InABox.Poster.Custom/ICustomPoster.cs

@@ -0,0 +1,10 @@
+using InABox.Core;
+
+namespace InABox.Poster.Custom
+{
+    [Caption("Custom")]
+    public interface ICustomPoster<TEntity> : IPoster<TEntity, CustomPosterSettings>
+        where TEntity : Entity, IPostable, IRemotable, IPersistent, new()
+    {
+    }
+}

+ 14 - 0
InABox.Poster.Custom/InABox.Poster.Custom.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\InABox.Core\InABox.Core.csproj" />
+    <ProjectReference Include="..\inabox.scripting\InABox.Scripting.csproj" />
+  </ItemGroup>
+
+</Project>

+ 24 - 0
InABox.Poster.Timberline/ITimberlinePoster.cs

@@ -0,0 +1,24 @@
+using InABox.Core;
+using InABox.Scripting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Timberline
+{
+    [Caption("Timberline")]
+    public interface ITimberlinePoster<TEntity, TSettings> : IPoster<TEntity, TSettings>
+        where TEntity : Entity, IPostable, IRemotable, IPersistent, new()
+        where TSettings : TimberlinePosterSettings<TEntity>
+    {
+        ScriptDocument? Script { set; }
+
+        bool BeforePost(IDataModel<TEntity> model);
+
+        bool Process(IDataModel<TEntity> model);
+
+        void AfterPost(IDataModel<TEntity> model);
+    }
+}

+ 14 - 0
InABox.Poster.Timberline/InABox.Poster.Timberline.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\InABox.Core\InABox.Core.csproj" />
+    <ProjectReference Include="..\inabox.scripting\InABox.Scripting.csproj" />
+  </ItemGroup>
+
+</Project>

+ 69 - 0
InABox.Poster.Timberline/TimberlinePosterEngine.cs

@@ -0,0 +1,69 @@
+using InABox.Core;
+using InABox.Core.Postable;
+using InABox.Scripting;
+using System;
+using System.Collections.Generic;
+using System.Formats.Asn1;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Timberline
+{
+    public class TimberlinePosterEngine<TPostable, TSettings> : PosterEngine<TPostable, ITimberlinePoster<TPostable, TSettings>, TSettings>
+        where TPostable : Entity, IPostable, IRemotable, IPersistent, new()
+        where TSettings : TimberlinePosterSettings<TPostable>, new()
+    {
+
+        private ScriptDocument? _script;
+        private bool _hasCheckedScript;
+
+        protected override ITimberlinePoster<TPostable, TSettings> CreatePoster()
+        {
+            var poster = base.CreatePoster();
+            poster.Script = GetScriptDocument();
+            return poster;
+        }
+
+        private ScriptDocument? GetScriptDocument()
+        {
+            if (_hasCheckedScript)
+            {
+                return _script;
+            }
+
+            var settings = GetSettings();
+            if (settings.ScriptEnabled && !string.IsNullOrWhiteSpace(settings.Script))
+            {
+                var document = new ScriptDocument(settings.Script);
+                if (!document.Compile())
+                {
+                    throw new Exception("Script failed to compile!");
+                }
+                _script = document;
+            }
+            else
+            {
+                _script = null;
+            }
+
+            _hasCheckedScript = true;
+            return _script;
+        }
+
+        public override bool BeforePost(IDataModel<TPostable> model)
+        {
+            return Poster.BeforePost(model);
+        }
+
+        protected override bool DoProcess(IDataModel<TPostable> model)
+        {
+            return Poster.Process(model);
+        }
+        public override void AfterPost(IDataModel<TPostable> model)
+        {
+            Poster.AfterPost(model);
+        }
+    }
+}

+ 16 - 0
InABox.Poster.Timberline/TimberlinePosterSettings.cs

@@ -0,0 +1,16 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Poster.Timberline
+{
+    public abstract class TimberlinePosterSettings<TEntity> : PosterSettings
+    {
+        protected abstract string DefaultScript();
+
+        public override string DefaultScript(Type TPostable) => DefaultScript();
+    }
+}

+ 19 - 8
InABox.Server/RPC/Transports/RPCServerTransport.cs

@@ -89,12 +89,17 @@ namespace InABox.Rpc
         /// <returns>The response to be sent back to the client.</returns>
         public RpcMessage? DoMessage(TConnection? connection, RpcMessage? message)
         {
-            var session = GetSession(connection);
-            DoBeforeMessage(session,message);
-            RpcMessage? response = null;
-            if (session != null)
+            if(message is null)
+            {
+                DoException(connection, new Exception("NULL Message Received"));
+                return null;
+            }
+            var response = new RpcMessage() { Id = message.Id, Command = message.Command };
+            try
             {
-                if (message != null)
+                var session = GetSession(connection);
+                DoBeforeMessage(session, message);
+                if (session != null)
                 {
                     response = new RpcMessage() { Id = message.Id, Command = message.Command };
 
@@ -118,10 +123,16 @@ namespace InABox.Rpc
                     DoAfterMessage(session, response);
                 }
                 else
-                    DoException(connection, new Exception("NULL Message Received"));
+                {
+                    DoException(connection, new Exception("Session not Found"));
+                    response.Error = RpcError.SESSIONNOTFOUND;
+                }
+            }
+            catch(Exception e)
+            {
+                DoException(connection, e);
+                response.Error = RpcError.SERVERERROR;
             }
-            else
-                DoException(connection, new Exception("Session not Found"));
 
             return response;
         }

+ 0 - 3
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -271,9 +271,6 @@ namespace InABox.DynamicGrid
                 if (!result.Items.Any(x => string.Equals(x.Property, col.Property)))
                     result.Add(col.Property);
 
-            var props = DatabaseSchema.Properties(typeof(TEntity))
-                .Where(x => x.Setter() != null)
-                .OrderBy(x => CoreUtils.GetPropertySequence(typeof(TEntity), x.Name));
             foreach (var col in result.Items)
             {
                 var prop = DatabaseSchema.Property(typeof(TEntity), col.Property);