From 444c9914c4241d1650e56ab2edaa060177a35685 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Sat, 9 Jun 2018 21:27:15 +0000 Subject: [PATCH] [NF] Moved CronJob, Overtaker, Statuspolling to Abstract Bot-Utils [NF] Create NSI Script to produce exe [NF] Add License.txt --- Bot-Utils.csproj | 3 + Moduls/CronJob.cs | 172 ++++++++++++++++++++++++++++++++++++++ Moduls/Overtaker.cs | 85 +++++++++++++++++++ Moduls/Statuspolling.cs | 53 ++++++++++++ bin/Release/Bot-Utils.dll | Bin 14848 -> 20480 bytes 5 files changed, 313 insertions(+) create mode 100644 Moduls/CronJob.cs create mode 100644 Moduls/Overtaker.cs create mode 100644 Moduls/Statuspolling.cs diff --git a/Bot-Utils.csproj b/Bot-Utils.csproj index 7c26c2d..7592403 100644 --- a/Bot-Utils.csproj +++ b/Bot-Utils.csproj @@ -55,7 +55,10 @@ + + + diff --git a/Moduls/CronJob.cs b/Moduls/CronJob.cs new file mode 100644 index 0000000..0959169 --- /dev/null +++ b/Moduls/CronJob.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Threading; +using BlubbFish.Utils.IoT.Bots.Interfaces; + +namespace BlubbFish.Utils.IoT.Bots.Moduls { + public abstract class CronJob : AModul, IDisposable, IForceLoad { + protected readonly List, Object>> internalCron = new List, Object>>(); + protected Thread thread; + protected DateTime crontime; + + protected readonly Dictionary cron_named = new Dictionary { + { "@yearly", "0 0 1 1 *" }, + { "@annually", "0 0 1 1 *" }, + { "@monthly", "0 0 1 * *" }, + { "@weekly", "0 0 * * 0" }, + { "@daily", "0 0 * * *" }, + { "@hourly", "0 * * * *" } + }; + + #region Constructor + public CronJob(T lib, InIReader settings) : base(lib, settings) { + this.crontime = DateTime.Now; + this.thread = new Thread(this.Runner); + this.thread.Start(); + } + #endregion + + #region Cronjobrunner + protected void Runner() { + Thread.Sleep(DateTime.Now.AddMinutes(1).AddSeconds(DateTime.Now.Second * (-1)).AddMilliseconds(DateTime.Now.Millisecond * (-1)) - DateTime.Now); + while (true) { + if (this.crontime.Minute != DateTime.Now.Minute) { + this.crontime = DateTime.Now; + if (this.config.Count != 0) { + foreach (KeyValuePair> item in this.config) { + if (item.Value.ContainsKey("cron") && item.Value.ContainsKey("set") && this.ParseCronString(item.Value["cron"])) { + this.SetValues(item.Value["set"]); + } + } + } + foreach (Tuple, Object> item in this.internalCron) { + if (this.ParseCronString(item.Item1)) { + item.Item2?.Invoke(item.Item3); + } + } + } + Thread.Sleep(100); + } + } + + protected abstract void SetValues(String value); + #endregion + + #region CronFunctions + protected Boolean ParseCronString(String cronstring) { + cronstring = cronstring.Trim(); + if (this.cron_named.ContainsKey(cronstring)) { + cronstring = this.cron_named[cronstring]; + } + String[] value = cronstring.Split(' '); + if (value.Length != 5) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("mm"), value[0], "0-59")) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("HH"), value[1], "0-23")) { + return false; + } + if (!this.CheckDateStr(this.crontime.ToString("MM"), value[3], "1-12")) { + return false; + } + if (value[2] != "*" && value[4] != "*") { + if (!this.CheckDateStr(this.crontime.ToString("dd"), value[2], "1-31") && !this.CheckDateStr(((Int32)this.crontime.DayOfWeek).ToString(), value[4], "0-7")) { + return false; + } + } else { + if (!this.CheckDateStr(this.crontime.ToString("dd"), value[2], "1-31")) { + return false; + } + if (!this.CheckDateStr(((Int32)this.crontime.DayOfWeek).ToString(), value[4], "0-7")) { + return false; + } + } + return true; + } + protected Boolean CheckDateStr(String date, String cron, String limit) { + cron = cron.ToLower(); + for (Int32 i = 0; i <= 6; i++) { + cron = cron.Replace(DateTime.Parse("2015-01-" + (4 + i) + "T00:00:00").ToString("ddd", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + cron = cron.Replace(DateTime.Parse("2015-01-" + (4 + i) + "T00:00:00").ToString("dddd", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + } + for (Int32 i = 1; i <= 12; i++) { + cron = cron.Replace(DateTime.Parse("2015-" + i + "-01T00:00:00").ToString("MMM", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + cron = cron.Replace(DateTime.Parse("2015-" + i + "-01T00:00:00").ToString("MMMM", CultureInfo.CreateSpecificCulture("en-US")), i.ToString()); + } + if (cron.Contains("*")) { + cron = cron.Replace("*", limit); + } + if (cron.Contains("-")) { + MatchCollection m = new Regex("(\\d+)-(\\d+)").Matches(cron); + foreach (Match p in m) { + List s = new List(); + for (Int32 i = Math.Min(Int32.Parse(p.Groups[1].Value), Int32.Parse(p.Groups[2].Value)); i <= Math.Max(Int32.Parse(p.Groups[1].Value), Int32.Parse(p.Groups[2].Value)); i++) { + s.Add(i.ToString()); + } + cron = cron.Replace(p.Groups[0].Value, String.Join(",", s)); + } + } + Int32 match = 0; + if (cron.Contains("/")) { + Match m = new Regex("/(\\d+)").Match(cron); + cron = cron.Replace(m.Groups[0].Value, ""); + match = Int32.Parse(m.Groups[1].Value); + } + Dictionary ret = new Dictionary(); + if (!cron.Contains(",")) { + ret.Add(Int32.Parse(cron), ""); + } else { + foreach (String item in cron.Split(',')) { + if (!ret.ContainsKey(Int32.Parse(item))) { + ret.Add(Int32.Parse(item), ""); + } + } + } + if (match != 0) { + Dictionary r = new Dictionary(); + foreach (KeyValuePair item in ret) { + if (item.Key % match == 0) { + r.Add(item.Key, ""); + } + } + ret = r; + } + return ret.ContainsKey(Int32.Parse(date)); + } + #endregion + + #region AModul + public override void SetInterconnection(String cron, Action hook, Object data) { + this.internalCron.Add(new Tuple, Object>(cron, hook, data)); + } + + 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() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/Moduls/Overtaker.cs b/Moduls/Overtaker.cs new file mode 100644 index 0000000..1a31a04 --- /dev/null +++ b/Moduls/Overtaker.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; + +namespace BlubbFish.Utils.IoT.Bots.Moduls { + public abstract class Overtaker : AModul, IDisposable { + protected readonly Dictionary> events = new Dictionary>(); + + #region Constructor + public Overtaker(T lib, InIReader settings) : base(lib, settings) { + this.ParseIni(); + } + #endregion + + #region Overtakerfunctions + protected void ParseIni() { + foreach (KeyValuePair> item in this.config) { + if (item.Value.ContainsKey("from")) { + String from = item.Value["from"]; + String[] source = from.Split(':'); + this.events.Add(source[0], item.Value); + this.LibraryUpdateHook(source[0]); + } + } + } + + protected void SetValues(Object sender, String name, Dictionary dictionary) { + String from = dictionary["from"]; + String[] source = from.Split(':'); + if (source.Length != 2) { + return; + } + String source_value; + if (sender.HasProperty(source[1])) { + source_value = sender.GetProperty(source[1]).ToString(); + } else { + return; + } + if (dictionary.ContainsKey("convert")) { + foreach (String tuple in dictionary["convert"].Split(';')) { + String[] item = tuple.Split('-'); + if (source_value == item[0]) { + source_value = item[1]; + } + } + } + if (dictionary.ContainsKey("to")) { + foreach (String to in dictionary["to"].Split(';')) { + String[] target = to.Split(':'); + if (target.Length == 2) { + this.SetValueHook(target[0], target[1], source_value); + } + } + } + } + + protected abstract void LibraryUpdateHook(String id); + + protected abstract void SetValueHook(String id, String prop, String value); + #endregion + + #region AModul + public override void Interconnect(Dictionary moduls) { } + 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() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/Moduls/Statuspolling.cs b/Moduls/Statuspolling.cs new file mode 100644 index 0000000..974cb54 --- /dev/null +++ b/Moduls/Statuspolling.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using BlubbFish.Utils.IoT.Bots.Interfaces; + +namespace BlubbFish.Utils.IoT.Bots.Moduls { + public abstract class Statuspolling : AModul, IDisposable, IForceLoad { + + #region Constructor + public Statuspolling(T lib, InIReader settings) : base(lib, settings) { } + #endregion + + #region Statuspollingfunctions + protected abstract void PollSpecific(Object obj); + + protected abstract void PollAll(Object obj); + #endregion + + #region AModul + public override void Interconnect(Dictionary moduls) { + foreach (KeyValuePair item in moduls) { + if (item.Value is CronJob) { + ((AModul)item.Value).SetInterconnection("0 0 * * *", new Action(this.PollAll), null); + if (this.config.Count != 0) { + foreach (KeyValuePair> section in this.config) { + if (section.Value.ContainsKey("cron") && section.Value.ContainsKey("devices")) { + ((AModul)item.Value).SetInterconnection(section.Value["cron"], new Action(this.PollSpecific), section.Value["devices"]); + } + } + } + } + } + } + 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() { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/bin/Release/Bot-Utils.dll b/bin/Release/Bot-Utils.dll index 42947bfa1cf24f9ab2d92381379a777d7d12e380..0498511d487ab0c891fadc5e16edd220584d517f 100644 GIT binary patch literal 20480 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 JvdyBNmW^1V|wM4iaDx27>~m5NIq}C?H#CrIiSl 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