using InABox.Clients; using InABox.Configuration; using InABox.Core.Postable; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace InABox.Core { public interface IPosterEngine where TPostable : Entity, IPostable, IRemotable, IPersistent, new() { IPostResult? Process(IDataModel model); } public interface IPosterEngine : IPosterEngine where TPostable : Entity, IPostable, IRemotable, IPersistent, new() where TPoster : IPoster where TSettings : PosterSettings, new() { } internal static class PosterEngineUtils { private static Type[]? _posters; public static Type GetPoster(Type TPoster) { _posters ??= CoreUtils.TypeList( AppDomain.CurrentDomain.GetAssemblies(), x => x.IsClass && !x.IsAbstract && !x.IsGenericType && x.HasInterface(typeof(IPoster<,>)) ).ToArray(); return _posters.Where(x => TPoster.IsAssignableFrom(x)).FirstOrDefault() ?? throw new Exception($"No poster of type {TPoster}."); } } /// /// A base class for all . A concrete instance of this will be loaded by /// ; a new instance is guaranteed to be created each time that method is called. /// /// /// /// public abstract class PosterEngine : IPosterEngine where TPostable : Entity, IPostable, IRemotable, IPersistent, new() where TPoster : class, IPoster where TSettings : PosterSettings, new() { protected TPoster Poster; public PosterEngine() { Poster = CreatePoster(); } private static readonly Type? PosterType = PosterEngineUtils.GetPoster(typeof(TPoster)); protected virtual TPoster CreatePoster() { var poster = (Activator.CreateInstance(PosterType!) as TPoster)!; poster.Settings = GetSettings(); return poster; } private TSettings? _settings; protected TSettings GetSettings() { _settings ??= PosterUtils.LoadPosterSettings(); return _settings; } protected void SaveSettings(TSettings settings) { _settings = settings; PosterUtils.SavePosterSettings(_settings); } /// /// Returns the , if is ; /// otherwise, returns . /// protected string? GetScript() { var settings = GetSettings(); return settings.ScriptEnabled ? settings.Script : null; } protected abstract IPostResult DoProcess(IDataModel model); /// /// Process the before loading; /// /// /// if the processing must be cancelled. public abstract bool BeforePost(IDataModel model); /// /// Prior to saving the entities, make any necessary changes to those entities. /// This is only called if returned . /// /// /// The result object returned by public abstract void AfterPost(IDataModel model, IPostResult result); private static void SetFailed(IList entities) { foreach (var post in entities) { post.PostedStatus = PostedStatus.PostFailed; post.PostedNote = "Post failed."; } new Client().Save(entities, "Post failed by user."); } public IPostResult? Process(IDataModel model) { if (!BeforePost(model)) { return null; } // Add posted flags; if the columns is null, then we don't need to worry, because it will be loaded. model.GetColumns()?.Add(x => x.PostedStatus) .Add(x => x.Posted) .Add(x => x.PostedNote); model.LoadModel(); var data = model.GetTable(); if (!data.Rows.Any()) { throw new EmptyPostException(); } if(data.Rows.Any(x => x.Get(x => x.PostedStatus) == PostedStatus.Posted)) { throw new RepostedException(); } try { var result = DoProcess(model); AfterPost(model, result); new Client().Save(result.PostedEntities, "Posted by user."); foreach (var (type, fragments) in result.Fragments) { Client.Create(type).Save(fragments.Cast(), ""); } return result; } catch (PostCancelledException) { var entities = data.ToObjects().ToList(); SetFailed(entities); throw; } catch (PostFailedMessageException) { throw; } catch(Exception e) { Logger.Send(LogType.Error, "", $"Post Failed: {CoreUtils.FormatException(e)}"); var entities = data.ToObjects().ToList(); SetFailed(entities); throw; } } } }