Compare commits

...

3 Commits

Author SHA1 Message Date
8e41d15e4e [1.2.6] Makeing Logging easyier 2022-01-25 23:28:11 +01:00
1862aa7da2 [1.2.5] Better linux handling 2022-01-20 19:20:04 +01:00
628db8db10 [1.2.4] Config enabled module loading 2022-01-18 22:00:43 +01:00
11 changed files with 280 additions and 306 deletions

View File

@ -1,71 +1,58 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading; using System.Threading;
namespace BlubbFish.Utils.IoT.Bots { namespace BlubbFish.Utils.IoT.Bots {
public abstract class ABot { public abstract class ABot {
#if !NETCOREAPP
private Thread sig_thread;
#endif
private Boolean RunningProcess = true; private Boolean RunningProcess = true;
private readonly ProgramLogger logger = null;
protected ProgramLogger logger = new ProgramLogger(); public Boolean DebugLogging {
get;
}
private void SetupShutdown(Object sender, ConsoleCancelEventArgs e) { public ABot(String[] _, Boolean fileLogging, String configSearchPath) {
InIReader.SetSearchPath(new List<String>() { "/etc/"+ configSearchPath, Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\"+ configSearchPath });
if(fileLogging) {
this.logger = new ProgramLogger(InIReader.GetInstance("settings").GetValue("logging", "path", Assembly.GetEntryAssembly().GetName().Name + ".log"));
}
if(Boolean.TryParse(InIReader.GetInstance("settings").GetValue("logging", "debug", "true"), out Boolean debuglog)) {
this.DebugLogging = debuglog;
}
}
private void ConsoleCancelEvent(Object sender, ConsoleCancelEventArgs e) {
e.Cancel = true; e.Cancel = true;
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.SetupShutdown: Signalhandler Windows INT recieved."); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ConsoleCancelEvent()");
this.RunningProcess = false; this.RunningProcess = false;
} }
#if NETCOREAPP #if NETCOREAPP
private void Default_Unloading(AssemblyLoadContext obj) { private void Unloading(AssemblyLoadContext obj) => this.RunningProcess = false;
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.SetupShutdown: Signalhandler Windows NETCORE recieved.");
this.RunningProcess = false; private void ProcessExit(Object sender, EventArgs e) => this.RunningProcess = false;
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Shutdown.");
this.Dispose();
}
#endif #endif
protected void WaitForShutdown() { protected void WaitForShutdown() {
if(Type.GetType("Mono.Runtime") != null) { #if NETCOREAPP
#if !NETCOREAPP AssemblyLoadContext.Default.Unloading += this.Unloading;
this.sig_thread = new Thread(delegate () { AppDomain.CurrentDomain.ProcessExit += this.ProcessExit;
Mono.Unix.UnixSignal[] signals = new Mono.Unix.UnixSignal[] { Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Attach Unloading and ProcessExit.");
new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGTERM), #endif
new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGINT) Console.CancelKeyPress += new ConsoleCancelEventHandler(this.ConsoleCancelEvent);
}; Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Attach ConsoleCancelEvent.");
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Signalhandler Mono attached.");
while(true) {
Int32 i = Mono.Unix.UnixSignal.WaitAny(signals, -1);
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Signalhandler Mono INT recieved " + i + ".");
this.RunningProcess = false;
break;
}
});
this.sig_thread.Start();
#endif
} else {
#if NETCOREAPP
AssemblyLoadContext.Default.Unloading += this.Default_Unloading;
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Signalhandler Netcore attached.");
#endif
Console.CancelKeyPress += new ConsoleCancelEventHandler(this.SetupShutdown);
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Signalhandler Windows attached.");
}
while(this.RunningProcess) { while(this.RunningProcess) {
Thread.Sleep(100); Thread.Sleep(100);
} }
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Shutdown."); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.WaitForShutdown: Shutdown.");
} }
public virtual void Dispose() { public virtual void Dispose() {
#if !NETCOREAPP Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.Dispose: Shutdown.");
if(this.sig_thread != null && this.sig_thread.IsAlive) { this.RunningProcess = false;
this.sig_thread.Abort(); this.logger?.Dispose();
}
#endif
} }
} }
} }

View File

@ -12,7 +12,7 @@ namespace BlubbFish.Utils.IoT.Bots {
protected HttpListener httplistener; protected HttpListener httplistener;
public AWebserver(Dictionary<String, String> settings) => this.config = settings; public AWebserver(String[] args, Boolean fileLogging, String configSearchPath, Dictionary<String, String> settings) : base(args, fileLogging, configSearchPath) => this.config = settings;
protected void StartListen() { protected void StartListen() {
this.httplistener = new HttpListener(); this.httplistener = new HttpListener();

View File

@ -7,7 +7,7 @@ using BlubbFish.Utils.IoT.Events;
namespace BlubbFish.Utils.IoT.Bots { namespace BlubbFish.Utils.IoT.Bots {
public abstract class AWebserverDataBackend : AWebserver { public abstract class AWebserverDataBackend : AWebserver {
protected ABackend databackend; protected ABackend databackend;
protected AWebserverDataBackend(ABackend backend, Dictionary<String, String> settings) : base(settings) => this.databackend = backend; protected AWebserverDataBackend(String[] args, Boolean fileLogging, String configSearchPath, ABackend backend, Dictionary<String, String> settings) : base(args, fileLogging, configSearchPath, settings) => this.databackend = backend;
protected void StartDataBackend() => this.databackend.MessageIncomming += this.Backend_MessageIncomming; protected void StartDataBackend() => this.databackend.MessageIncomming += this.Backend_MessageIncomming;

View File

@ -5,32 +5,35 @@
<RootNamespace>BlubbFish.Utils.IoT.Bots</RootNamespace> <RootNamespace>BlubbFish.Utils.IoT.Bots</RootNamespace>
<AssemblyName>Bot-Utils</AssemblyName> <AssemblyName>Bot-Utils</AssemblyName>
<PackageId>Bots.IoT.Utils.BlubbFish</PackageId> <PackageId>Bots.IoT.Utils.BlubbFish</PackageId>
<Version>1.2.3</Version> <Version>1.2.6</Version>
<AssemblyVersion>1.2.3</AssemblyVersion>
<FileVersion>1.2.3</FileVersion>
<NeutralLanguage>de-DE</NeutralLanguage> <NeutralLanguage>de-DE</NeutralLanguage>
<Description>Bot-Utils are helpers for programming a bot</Description> <Description>Bot-Utils are helpers for programming a bot</Description>
<Authors>BlubbFish</Authors> <Authors>BlubbFish</Authors>
<Company>BlubbFish</Company> <Company>BlubbFish</Company>
<Copyright>Copyright © BlubbFish 2018 - 09.01.2022</Copyright> <Copyright>Copyright © BlubbFish 2018 - 25.01.2022</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>http://git.blubbfish.net/vs_utils/Bot-Utils</PackageProjectUrl> <PackageProjectUrl>http://git.blubbfish.net/vs_utils/Bot-Utils</PackageProjectUrl>
<RepositoryUrl>http://git.blubbfish.net/vs_utils/Bot-Utils.git</RepositoryUrl> <RepositoryUrl>http://git.blubbfish.net/vs_utils/Bot-Utils.git</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageReleaseNotes>1.2.3 Tiny Refactoring <PackageReleaseNotes>
1.2.2 Going to netcore 1.2.6 - 2022-01-25 - Makeing Logging easyier
1.2.1 When using Dispose, kill also mqtt connection and other tiny fixes 1.2.5 - 2022-01-20 - Better linux handling
1.2.0 Refactor Bot to ABot and refere MultiSourceBot, Webserver and Bot to it. Add MultiSourceBot. Rewrite Mqtt module so that it not need to watch the connection. 1.2.4 - 2022-01-18 - Config enabled module loading
1.1.9 Modify Output of SendFileResponse 1.2.3 - 2022-01-09 - Tiny Refactoring
1.1.8 Add logger to Webserver Class 1.2.2 - 2021-08-22 - Going to netcore
1.1.7 Restrucutre loading, so that all is init and after the listener is started, REQUEST_URL_HOST gives now host and port 1.2.1 - 2019-08-30 - When using Dispose, kill also mqtt connection and other tiny fixes
1.1.6 rename functions and make SendFileResponse with a parameter for the folder (default resources), also put returntype boolean, add function that parse post params, if path is a dictionary try to load index.html 1.2.0 - 2019-05-27 - Refactor Bot to ABot and refere MultiSourceBot
1.1.5 add a function to send an object as json directly 1.1.9 - 2019-04-21 - Modify Output of SendFileResponse
1.1.4 add Woff as Binary type 1.1.8 - 2019-04-15 - Add logger to Webserver Class
1.1.3 Variables parsing now as a String 1.1.7 - 2019-04-14 - REQUEST_URL_HOST
1.1.2 Fixing bug for Contenttype 1.1.6 - 2019-04-03 - Refactoring and bugfixing
1.1.1 Update to local librarys 1.1.5 - 2019-03-27 - add a function to send an object as json directly
1.1.0 Remove Helper from Bot-Utils</PackageReleaseNotes> 1.1.4 - 2019-03-13 - add Woff as Binary type
1.1.3 - 2019-03-10 - Variables parsing now as a String
1.1.2 - 2019-03-08 - Fixing bug for Contenttype
1.1.1 - 2019-02-17 - Update to local librarys
1.1.0 - 2019-02-14 - Remove Helper from Bot-Utils
</PackageReleaseNotes>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -9,8 +9,11 @@ namespace BlubbFish.Utils.IoT.Bots {
public abstract class Bot<T> : ABot { public abstract class Bot<T> : ABot {
protected readonly Dictionary<String, AModul<T>> moduls = new Dictionary<String, AModul<T>>(); protected readonly Dictionary<String, AModul<T>> moduls = new Dictionary<String, AModul<T>>();
public Bot(String[] args, Boolean fileLogging, String configSearchPath) : base(args, fileLogging, configSearchPath) { }
protected void ModulDispose() { protected void ModulDispose() {
foreach (KeyValuePair<String, AModul<T>> item in this.moduls) { foreach (KeyValuePair<String, AModul<T>> item in this.moduls) {
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulDispose: Entlade Modul: " + item.Key);
item.Value.Dispose(); item.Value.Dispose();
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulDispose: Modul entladen: " + item.Key); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulDispose: Modul entladen: " + item.Key);
} }
@ -25,13 +28,18 @@ namespace BlubbFish.Utils.IoT.Bots {
String name = t.Name; String name = t.Name;
try { try {
if (InIReader.ConfigExist(name.ToLower())) { if (InIReader.ConfigExist(name.ToLower())) {
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Load Modul " + name); Dictionary<String, String> modulconfig = InIReader.GetInstance(name.ToLower()).GetSection("modul");
this.moduls.Add(name, (AModul<T>)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, InIReader.GetInstance(name.ToLower()) })); if(!(modulconfig.ContainsKey("enabled") && modulconfig["enabled"].ToLower() == "false")) {
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Loaded Modul " + name); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Load Modul " + name);
} else if (t.HasInterface(typeof(IForceLoad))) { this.moduls.Add(name, (AModul<T>)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, InIReader.GetInstance(name.ToLower()) }));
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Load Modul Forced " + name); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Loaded Modul " + name);
continue;
}
}
if (t.HasInterface(typeof(IForceLoad))) {
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Forced Load Modul " + name);
this.moduls.Add(name, (AModul<T>)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, null })); this.moduls.Add(name, (AModul<T>)t.GetConstructor(new Type[] { typeof(T), typeof(InIReader) }).Invoke(new Object[] { library, null }));
Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Loaded Modul Forced " + name); Console.WriteLine("BlubbFish.Utils.IoT.Bots.Bot.ModulLoader: Forced Loaded Modul " + name);
} }
} catch(Exception e) { } catch(Exception e) {
Helper.WriteError(e.InnerException.Message); Helper.WriteError(e.InnerException.Message);
@ -55,6 +63,10 @@ namespace BlubbFish.Utils.IoT.Bots {
} }
} }
protected void ModulUpdate(Object sender, ModulEventArgs e) => Console.WriteLine(e.ToString()); protected void ModulUpdate(Object sender, ModulEventArgs e) {
if(this.DebugLogging) {
Console.WriteLine(e.ToString());
}
}
} }
} }

View File

@ -3,12 +3,14 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using BlubbFish.Utils.IoT.Bots.Interfaces; using BlubbFish.Utils.IoT.Bots.Interfaces;
namespace BlubbFish.Utils.IoT.Bots.Moduls { namespace BlubbFish.Utils.IoT.Bots.Moduls {
public abstract class CronJob<T> : AModul<T>, IDisposable, IForceLoad { public abstract class CronJob<T> : AModul<T>, IForceLoad {
protected readonly List<Tuple<String, Action<Object>, Object>> internalCron = new List<Tuple<String, Action<Object>, Object>>(); protected readonly List<Tuple<String, Action<Object>, Object>> internalCron = new List<Tuple<String, Action<Object>, Object>>();
protected Thread thread; protected Thread thread;
protected Boolean threadRunning = false;
protected DateTime crontime; protected DateTime crontime;
protected readonly Dictionary<String, String> cron_named = new Dictionary<String, String> { protected readonly Dictionary<String, String> cron_named = new Dictionary<String, String> {
@ -24,14 +26,18 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
public CronJob(T lib, InIReader settings) : base(lib, settings) { public CronJob(T lib, InIReader settings) : base(lib, settings) {
this.crontime = DateTime.Now; this.crontime = DateTime.Now;
this.thread = new Thread(this.Runner); this.thread = new Thread(this.Runner);
this.threadRunning = true;
this.thread.Start(); this.thread.Start();
} }
#endregion #endregion
#region Cronjobrunner #region Cronjobrunner
protected void Runner() { protected void Runner() {
Thread.Sleep(DateTime.Now.AddMinutes(1).AddSeconds(DateTime.Now.Second * -1).AddMilliseconds(DateTime.Now.Millisecond * -1) - DateTime.Now); DateTime nextminute = DateTime.Now.AddMinutes(1).AddSeconds(DateTime.Now.Second * -1).AddMilliseconds(DateTime.Now.Millisecond * -1);
while (true) { while(nextminute > DateTime.Now && this.threadRunning) {
Thread.Sleep(100);
}
while (this.threadRunning) {
if (this.crontime.Minute != DateTime.Now.Minute) { if (this.crontime.Minute != DateTime.Now.Minute) {
this.crontime = DateTime.Now; this.crontime = DateTime.Now;
if (this.config.Count != 0) { if (this.config.Count != 0) {
@ -143,27 +149,13 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
public override void SetInterconnection(String cron, Action<Object> hook, Object data) => this.internalCron.Add(new Tuple<String, Action<Object>, Object>(cron, hook, data)); public override void SetInterconnection(String cron, Action<Object> hook, Object data) => this.internalCron.Add(new Tuple<String, Action<Object>, Object>(cron, hook, data));
protected override void UpdateConfig() { } protected override void UpdateConfig() { }
#endregion
#region IDisposable Support
private Boolean disposedValue = false;
protected virtual void Dispose(Boolean disposing) {
if (!this.disposedValue) {
if (disposing) {
if (this.thread != null) {
this.thread.Abort();
while (this.thread.ThreadState == ThreadState.Running) { Thread.Sleep(100); }
}
}
this.thread = null;
this.disposedValue = true;
}
}
public override void Dispose() { public override void Dispose() {
this.Dispose(true); this.threadRunning = false;
GC.SuppressFinalize(this); while(this.thread != null && this.thread.IsAlive) {
Thread.Sleep(10);
}
this.thread = null;
} }
#endregion #endregion
} }

View File

@ -7,7 +7,7 @@ using BlubbFish.Utils.IoT.Events;
using LitJson; using LitJson;
namespace BlubbFish.Utils.IoT.Bots.Moduls { namespace BlubbFish.Utils.IoT.Bots.Moduls {
public abstract class Mqtt<T> : AModul<T>, IDisposable { public abstract class Mqtt<T> : AModul<T> {
protected ABackend mqtt; protected ABackend mqtt;
protected Dictionary<String, AModul<T>> modules; protected Dictionary<String, AModul<T>> modules;
@ -34,6 +34,8 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
public override void Interconnect(Dictionary<String, AModul<T>> moduls) => this.modules = moduls; public override void Interconnect(Dictionary<String, AModul<T>> moduls) => this.modules = moduls;
protected override void UpdateConfig() => this.Reconnect(); protected override void UpdateConfig() => this.Reconnect();
public override void Dispose() => this.Disconnect();
#endregion #endregion
protected Tuple<Boolean, MqttEvent> ChangeConfig(BackendEvent e, String topic) { protected Tuple<Boolean, MqttEvent> ChangeConfig(BackendEvent e, String topic) {
@ -74,23 +76,5 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
} }
return new Tuple<Boolean, MqttEvent>(false, null); return new Tuple<Boolean, MqttEvent>(false, null);
} }
#region IDisposable Support
private Boolean disposedValue = false;
protected void Dispose(Boolean disposing) {
if (!this.disposedValue) {
if (disposing) {
this.Disconnect();
}
this.disposedValue = true;
}
}
public override void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
} }
} }

View File

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlubbFish.Utils.IoT.Bots.Moduls { namespace BlubbFish.Utils.IoT.Bots.Moduls {
public abstract class Overtaker<T> : AModul<T>, IDisposable { public abstract class Overtaker<T> : AModul<T> {
protected readonly Dictionary<String, Dictionary<String, String>> events = new Dictionary<String, Dictionary<String, String>>(); protected readonly Dictionary<String, Dictionary<String, String>> events = new Dictionary<String, Dictionary<String, String>>();
#region Constructor #region Constructor
@ -62,22 +62,7 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
#region AModul #region AModul
public override void Interconnect(Dictionary<String, AModul<T>> moduls) { } public override void Interconnect(Dictionary<String, AModul<T>> moduls) { }
protected override void UpdateConfig() => this.ParseIni(); protected override void UpdateConfig() => this.ParseIni();
#endregion
#region IDisposable Support
private Boolean disposedValue = false;
protected virtual void Dispose(Boolean disposing) {
if (!this.disposedValue) {
if (disposing) {
}
this.disposedValue = true;
}
}
public override void Dispose() { public override void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
} }
#endregion #endregion
} }

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using BlubbFish.Utils.IoT.Bots.Interfaces; using BlubbFish.Utils.IoT.Bots.Interfaces;
namespace BlubbFish.Utils.IoT.Bots.Moduls { namespace BlubbFish.Utils.IoT.Bots.Moduls {
public abstract class Statuspolling<T> : AModul<T>, IDisposable, IForceLoad { public abstract class Statuspolling<T> : AModul<T>, IForceLoad {
#region Constructor #region Constructor
public Statuspolling(T lib, InIReader settings) : base(lib, settings) { } public Statuspolling(T lib, InIReader settings) : base(lib, settings) { }
@ -31,22 +31,7 @@ namespace BlubbFish.Utils.IoT.Bots.Moduls {
} }
} }
protected override void UpdateConfig() { } protected override void UpdateConfig() { }
#endregion
#region IDisposable Support
private Boolean disposedValue = false;
protected virtual void Dispose(Boolean disposing) {
if (!this.disposedValue) {
if (disposing) {
}
this.disposedValue = true;
}
}
public override void Dispose() { public override void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
} }
#endregion #endregion
} }

View File

@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BlubbFish.Utils.IoT.Connector; using BlubbFish.Utils.IoT.Connector;
namespace BlubbFish.Utils.IoT.Bots { namespace BlubbFish.Utils.IoT.Bots {
@ -10,7 +8,7 @@ namespace BlubbFish.Utils.IoT.Bots {
protected Dictionary<String, ABackend> sources; protected Dictionary<String, ABackend> sources;
protected Dictionary<String, String> settings; protected Dictionary<String, String> settings;
protected MultiSourceBot(Dictionary<String, ABackend> sources, Dictionary<String, String> settings) { protected MultiSourceBot(String[] args, Boolean fileLogging, String configSearchPath, Dictionary<String, ABackend> sources, Dictionary<String, String> settings) : base(args, fileLogging, configSearchPath) {
this.sources = sources; this.sources = sources;
this.settings = settings; this.settings = settings;
} }

View File

@ -1,5 +1,33 @@
# Changelog # Changelog
## 1.2.6 - 2022-01-25 - Makeing Logging easyier
### New Features
* Construct the Programm logger only if enabled
* Set Searchpath by default for a Bot
* Add an option that allows to debug logging (by default on)
### Bugfixes
### Changes
* Deconstruct Programmlogger
* Change all classes that extend ABot
* Module messages also uses debug logging flag
* Codingstyles
## 1.2.5 - 2022-01-20 - Better linux handling
### New Features
* Add ProcessExit Handler in ABot
* Add Output in ModulDispose
### Bugfixes
* Eleminate Hangs in Cronjob, when in startup phase it not blocks shutdown
### Changes
* Reweite ABot, remove Mono Code
* Codingstyle
## 1.2.4 - 2022-01-18 - Config enabled module loading
### New Features
* Modules can have an enabled=true|false in config, so that also enables or disables moduleloading.
### Bugfixes
### Changes
## 1.2.3 - 2022-01-09 - Tiny Refactoring ## 1.2.3 - 2022-01-09 - Tiny Refactoring
### New Features ### New Features
### Bugfixes ### Bugfixes