From d7852e45c9045f1ffb2843750cb7274c1eab73d2 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 12:53:03 +0100 Subject: [PATCH 01/13] add missing EnsureStartsWith --- csharp/Lib/Utils/StringUtils.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/csharp/Lib/Utils/StringUtils.cs b/csharp/Lib/Utils/StringUtils.cs index e0ee21746..46372e381 100644 --- a/csharp/Lib/Utils/StringUtils.cs +++ b/csharp/Lib/Utils/StringUtils.cs @@ -352,6 +352,34 @@ public static class StringUtils } + public static String TrimStart(this String target, String trimString) + { + if (String.IsNullOrEmpty(trimString)) return target; + + var result = target; + while (result.StartsWith(trimString)) + result = result[trimString.Length..]; + + return result; + } + + + public static String TrimEnd(this String target, String trimString) + { + if (String.IsNullOrEmpty(trimString)) + return target; + + var result = target; + while (result.EndsWith(trimString)) + result = result[..^trimString.Length]; + + return result; + } + + public static String EnsureStartsWith(this String target, String prefix) + { + return $"{prefix}{target.TrimStart(prefix)}"; + } } \ No newline at end of file From c3b1484e997e66e7b89be067cdf0eb4ca4f331b7 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 13:22:57 +0100 Subject: [PATCH 02/13] simplify units, give up on idea of automatic aggregation --- csharp/Lib/Units/Angle.cs | 2 +- csharp/Lib/Units/Angle.generated.cs | 38 +------- csharp/Lib/Units/ApparentPower.cs | 2 +- csharp/Lib/Units/ApparentPower.generated.cs | 38 +------- csharp/Lib/Units/Composite/Ac1Bus.cs | 28 +++--- csharp/Lib/Units/Composite/AcPhase.cs | 72 +++++++------- csharp/Lib/Units/Current.cs | 3 +- csharp/Lib/Units/Current.generated.cs | 38 +------- csharp/Lib/Units/Energy.cs | 3 +- csharp/Lib/Units/Energy.generated.cs | 38 +------- csharp/Lib/Units/Frequency.cs | 3 +- csharp/Lib/Units/Frequency.generated.cs | 38 +------- csharp/Lib/Units/Generator/EqualAttribute.cs | 5 - .../Lib/Units/Generator/GenerateAttribute.cs | 6 ++ csharp/Lib/Units/Generator/MeanAttribute.cs | 5 - csharp/Lib/Units/Generator/SumAttribute.cs | 5 - csharp/Lib/Units/Generator/Template.txt | 36 +------ csharp/Lib/Units/Generator/generate.sh | 2 +- csharp/Lib/Units/Number.cs | 13 --- csharp/Lib/Units/Number.generated.cs | 97 ------------------- csharp/Lib/Units/Percent.cs | 39 +------- csharp/Lib/Units/Power.cs | 3 +- csharp/Lib/Units/Power.generated.cs | 38 +------- csharp/Lib/Units/ReactivePower.cs | 2 +- csharp/Lib/Units/ReactivePower.generated.cs | 38 +------- csharp/Lib/Units/Resistance.cs | 3 +- csharp/Lib/Units/Resistance.generated.cs | 38 +------- csharp/Lib/Units/State.cs | 44 --------- csharp/Lib/Units/StateOfT.cs | 27 ------ csharp/Lib/Units/Temperature.cs | 6 +- csharp/Lib/Units/Temperature.generated.cs | 38 +------- csharp/Lib/Units/Units.cs | 24 ++--- csharp/Lib/Units/Voltage.cs | 3 +- csharp/Lib/Units/Voltage.generated.cs | 38 +------- 34 files changed, 139 insertions(+), 674 deletions(-) delete mode 100644 csharp/Lib/Units/Generator/EqualAttribute.cs create mode 100644 csharp/Lib/Units/Generator/GenerateAttribute.cs delete mode 100644 csharp/Lib/Units/Generator/MeanAttribute.cs delete mode 100644 csharp/Lib/Units/Generator/SumAttribute.cs delete mode 100644 csharp/Lib/Units/Number.cs delete mode 100644 csharp/Lib/Units/Number.generated.cs delete mode 100644 csharp/Lib/Units/State.cs delete mode 100644 csharp/Lib/Units/StateOfT.cs diff --git a/csharp/Lib/Units/Angle.cs b/csharp/Lib/Units/Angle.cs index b8972c826..87a3ed2b4 100644 --- a/csharp/Lib/Units/Angle.cs +++ b/csharp/Lib/Units/Angle.cs @@ -4,7 +4,7 @@ using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Units; -[Sum] +[Generate] public readonly partial struct Angle { public static String Unit => "rad"; diff --git a/csharp/Lib/Units/Angle.generated.cs b/csharp/Lib/Units/Angle.generated.cs index 837f0b2a2..75f16b601 100644 --- a/csharp/Lib/Units/Angle.generated.cs +++ b/csharp/Lib/Units/Angle.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Angle public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/ApparentPower.cs b/csharp/Lib/Units/ApparentPower.cs index 8c92385de..b2a5bba6c 100644 --- a/csharp/Lib/Units/ApparentPower.cs +++ b/csharp/Lib/Units/ApparentPower.cs @@ -2,7 +2,7 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -[Sum] +[Generate] public readonly partial struct ApparentPower { public static String Unit => "VA"; diff --git a/csharp/Lib/Units/ApparentPower.generated.cs b/csharp/Lib/Units/ApparentPower.generated.cs index a72053726..7f5a9b9c5 100644 --- a/csharp/Lib/Units/ApparentPower.generated.cs +++ b/csharp/Lib/Units/ApparentPower.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct ApparentPower public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Composite/Ac1Bus.cs b/csharp/Lib/Units/Composite/Ac1Bus.cs index f2c769f24..b968d72e9 100644 --- a/csharp/Lib/Units/Composite/Ac1Bus.cs +++ b/csharp/Lib/Units/Composite/Ac1Bus.cs @@ -6,20 +6,20 @@ public record Ac1Bus : AcPhase { public Frequency Frequency { get; init; } - [SuppressMessage("ReSharper", "RedundantCast")] - public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right) - { - var f = left.Frequency | right.Frequency; - var p = (AcPhase)left | (AcPhase)right; - - return new Ac1Bus - { - Frequency = f, - Current = p.Current, - Voltage = p.Voltage, - Phi = p.Phi - }; - } + // [SuppressMessage("ReSharper", "RedundantCast")] + // public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right) + // { + // var f = left.Frequency | right.Frequency; + // var p = (AcPhase)left | (AcPhase)right; + // + // return new Ac1Bus + // { + // Frequency = f, + // Current = p.Current, + // Voltage = p.Voltage, + // Phi = p.Phi + // }; + // } } diff --git a/csharp/Lib/Units/Composite/AcPhase.cs b/csharp/Lib/Units/Composite/AcPhase.cs index d590fbdee..8186e01f9 100644 --- a/csharp/Lib/Units/Composite/AcPhase.cs +++ b/csharp/Lib/Units/Composite/AcPhase.cs @@ -22,45 +22,45 @@ public record AcPhase : IBus public Angle Phi { get; init; } public ApparentPower ApparentPower => Voltage.Value * Current.Value ; - public Power ActivePower => ApparentPower.Value * PowerFactor.Value; + public Power ActivePower => ApparentPower.Value * PowerFactor; public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi); - public Number PowerFactor => Cos(Phi); + public Decimal PowerFactor => Cos(Phi); - public static AcPhase operator |(AcPhase left, AcPhase right) - { - // the Voltages of two phases are expected to be in phase and equal - - var v = left.Voltage | right.Voltage; - - // currents (RMS) can be different and out of phase - // https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/ - - // IF - // left(t) = ILeft sin(ωt) - // right(t) = IRight sin(ωt + φ). - // sum(t) = left(t) + right(t) = ISum sin(ωt + ψ). - - // THEN - // ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ). - // C = IRight * sin(φ) / sin(ψ). - - // in this calculation left(t) has zero phase shift. - // we can shift both waves by -left.Phi, so - // φ := right.phi - left.phi - - - var phi = right.Phi - left.Phi; - var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi)); - var iSum = right.Current * Sin(phi) / Sin(phiSum); - - return new AcPhase - { - Voltage = v, - Current = iSum, - Phi = phiSum - }; - } + // public static AcPhase operator |(AcPhase left, AcPhase right) + // { + // // the Voltages of two phases are expected to be in phase and equal + // + // var v = left.Voltage | right.Voltage; + // + // // currents (RMS) can be different and out of phase + // // https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/ + // + // // IF + // // left(t) = ILeft sin(ωt) + // // right(t) = IRight sin(ωt + φ). + // // sum(t) = left(t) + right(t) = ISum sin(ωt + ψ). + // + // // THEN + // // ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ). + // // C = IRight * sin(φ) / sin(ψ). + // + // // in this calculation left(t) has zero phase shift. + // // we can shift both waves by -left.Phi, so + // // φ := right.phi - left.phi + // + // + // var phi = right.Phi - left.Phi; + // var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi)); + // var iSum = right.Current * Sin(phi) / Sin(phiSum); + // + // return new AcPhase + // { + // Voltage = v, + // Current = iSum, + // Phi = phiSum + // }; + // } } \ No newline at end of file diff --git a/csharp/Lib/Units/Current.cs b/csharp/Lib/Units/Current.cs index b3c36ee34..07b473c24 100644 --- a/csharp/Lib/Units/Current.cs +++ b/csharp/Lib/Units/Current.cs @@ -2,8 +2,7 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; - -[Sum] +[Generate] public readonly partial struct Current { public static String Unit => "A"; diff --git a/csharp/Lib/Units/Current.generated.cs b/csharp/Lib/Units/Current.generated.cs index c06cb3ff0..de5242e57 100644 --- a/csharp/Lib/Units/Current.generated.cs +++ b/csharp/Lib/Units/Current.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Current public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Energy.cs b/csharp/Lib/Units/Energy.cs index 006568b06..ab05ba6c4 100644 --- a/csharp/Lib/Units/Energy.cs +++ b/csharp/Lib/Units/Energy.cs @@ -1,10 +1,9 @@ using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Units.Generator; - namespace InnovEnergy.Lib.Units; -[Sum] +[Generate] public readonly partial struct Energy { public static String Unit => "kWh"; diff --git a/csharp/Lib/Units/Energy.generated.cs b/csharp/Lib/Units/Energy.generated.cs index 90496238b..f0713a828 100644 --- a/csharp/Lib/Units/Energy.generated.cs +++ b/csharp/Lib/Units/Energy.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Energy public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Frequency.cs b/csharp/Lib/Units/Frequency.cs index 3041325c2..f34b83334 100644 --- a/csharp/Lib/Units/Frequency.cs +++ b/csharp/Lib/Units/Frequency.cs @@ -2,7 +2,8 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -[Equal] + +[Generate] public readonly partial struct Frequency { public static String Unit => "Hz"; diff --git a/csharp/Lib/Units/Frequency.generated.cs b/csharp/Lib/Units/Frequency.generated.cs index 576e7b1a4..fead5a326 100644 --- a/csharp/Lib/Units/Frequency.generated.cs +++ b/csharp/Lib/Units/Frequency.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Equal +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Frequency public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Generator/EqualAttribute.cs b/csharp/Lib/Units/Generator/EqualAttribute.cs deleted file mode 100644 index c0131044a..000000000 --- a/csharp/Lib/Units/Generator/EqualAttribute.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace InnovEnergy.Lib.Units.Generator; - -[AttributeUsage(AttributeTargets.Struct)] -internal class EqualAttribute: Attribute -{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/GenerateAttribute.cs b/csharp/Lib/Units/Generator/GenerateAttribute.cs new file mode 100644 index 000000000..12337cf50 --- /dev/null +++ b/csharp/Lib/Units/Generator/GenerateAttribute.cs @@ -0,0 +1,6 @@ +using System.Text.Json.Serialization; + +namespace InnovEnergy.Lib.Units.Generator; + +internal class GenerateAttribute : Attribute +{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/MeanAttribute.cs b/csharp/Lib/Units/Generator/MeanAttribute.cs deleted file mode 100644 index 161da8b0e..000000000 --- a/csharp/Lib/Units/Generator/MeanAttribute.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace InnovEnergy.Lib.Units.Generator; - -[AttributeUsage(AttributeTargets.Struct)] -internal class MeanAttribute: Attribute -{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/SumAttribute.cs b/csharp/Lib/Units/Generator/SumAttribute.cs deleted file mode 100644 index c4af9a156..000000000 --- a/csharp/Lib/Units/Generator/SumAttribute.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace InnovEnergy.Lib.Units.Generator; - -[AttributeUsage(AttributeTargets.Struct)] -internal class SumAttribute: Attribute -{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Template.txt b/csharp/Lib/Units/Generator/Template.txt index 38e30aa04..01f7220d4 100644 --- a/csharp/Lib/Units/Generator/Template.txt +++ b/csharp/Lib/Units/Generator/Template.txt @@ -22,40 +22,12 @@ public readonly partial struct Template public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Generator/generate.sh b/csharp/Lib/Units/Generator/generate.sh index 74cc6a0c4..ec9f7abce 100755 --- a/csharp/Lib/Units/Generator/generate.sh +++ b/csharp/Lib/Units/Generator/generate.sh @@ -4,7 +4,7 @@ scriptDir=$( dirname -- "$0"; ) cd "$scriptDir/.." || exit -for match in $(grep -e '\[Sum\]\|\[Equal\]\|\[Mean\]' -o *.cs | tr -d '[]') +for match in $(grep -e '\[Generate\]' -o *.cs | tr -d '[]') do path="${match%:*}" type="${match#*:}" diff --git a/csharp/Lib/Units/Number.cs b/csharp/Lib/Units/Number.cs deleted file mode 100644 index fdc9b3b42..000000000 --- a/csharp/Lib/Units/Number.cs +++ /dev/null @@ -1,13 +0,0 @@ -using InnovEnergy.Lib.Units.Generator; - - -namespace InnovEnergy.Lib.Units; - -[Sum] -public readonly partial struct Number -{ - public static String Unit => ""; - public static String Symbol => ""; - - public Number(Decimal value) => Value = value; -} \ No newline at end of file diff --git a/csharp/Lib/Units/Number.generated.cs b/csharp/Lib/Units/Number.generated.cs deleted file mode 100644 index 4c6eb5693..000000000 --- a/csharp/Lib/Units/Number.generated.cs +++ /dev/null @@ -1,97 +0,0 @@ -#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum - -using static System.Math; -using System.Text.Json; -using System.Text.Json.Serialization; -using InnovEnergy.Lib.Utils; - -namespace InnovEnergy.Lib.Units; - -using T = Number; - -[JsonConverter(typeof(NumberConverter))] -public readonly partial struct Number -{ - public Decimal Value { get; } - public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; - - // scalar multiplication - - public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); - public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); - public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - - // parallel - - #if Sum - - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - - // compare - - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); - -} - - -internal class NumberConverter : JsonConverter -{ - public override Number Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new Number(reader.GetDecimal()); - } - - public override void Write(Utf8JsonWriter writer, Number value, JsonSerializerOptions options) - { - var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); - - writer.WriteNumberValue(rounded); - } -} \ No newline at end of file diff --git a/csharp/Lib/Units/Percent.cs b/csharp/Lib/Units/Percent.cs index 577dec8a0..e20bc0c22 100644 --- a/csharp/Lib/Units/Percent.cs +++ b/csharp/Lib/Units/Percent.cs @@ -1,43 +1,12 @@ +using InnovEnergy.Lib.Units.Generator; + namespace InnovEnergy.Lib.Units; -using T = Percent; - -public readonly struct Percent +[Generate] +public readonly partial struct Percent { public static String Unit => "%"; public static String Symbol => "%"; // ?? public Percent(Decimal value) => Value = value; - - // not generated - // TODO: generate? - - public Decimal Value { get; } - public override String ToString() => Value + Unit; - - // scalar multiplication - public static Decimal operator *(Decimal scalar, T t) => scalar * t.Value / 100m; - public static Decimal operator *(T t, Decimal scalar) => scalar * t.Value / 100m; - - // parallel - public static Percent operator |(T left, T right) => new T((left.Value + right.Value) / 2m); - - // compare - public static Boolean operator ==(T left, T right) => left.Value == right.Value; - public static Boolean operator !=(T left, T right) => left.Value != right.Value; - public static Boolean operator > (T left, T right) => left.Value > right.Value; - public static Boolean operator < (T left, T right) => left.Value < right.Value; - public static Boolean operator >=(T left, T right) => left.Value >= right.Value; - public static Boolean operator <=(T left, T right) => left.Value <= right.Value; - - // conversion - public static implicit operator T(Decimal d) => new T(d); - public static implicit operator T(Double d) => new T((Decimal)d); - public static implicit operator T(Int32 i) => new T(i); - public static implicit operator Decimal(T t) => t.Value; - - // equality - public Boolean Equals(T other) => Value == other.Value; - public override Boolean Equals(Object? obj) => obj is T other && Equals(other); - public override Int32 GetHashCode() => Value.GetHashCode(); } \ No newline at end of file diff --git a/csharp/Lib/Units/Power.cs b/csharp/Lib/Units/Power.cs index de4a7f9ab..5eb088024 100644 --- a/csharp/Lib/Units/Power.cs +++ b/csharp/Lib/Units/Power.cs @@ -1,10 +1,9 @@ - using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -[Sum] +[Generate] public readonly partial struct Power { public static String Unit => "W"; diff --git a/csharp/Lib/Units/Power.generated.cs b/csharp/Lib/Units/Power.generated.cs index 1e68ec3a8..dc8ff2446 100644 --- a/csharp/Lib/Units/Power.generated.cs +++ b/csharp/Lib/Units/Power.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Power public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/ReactivePower.cs b/csharp/Lib/Units/ReactivePower.cs index 30d7641b9..a7017cb32 100644 --- a/csharp/Lib/Units/ReactivePower.cs +++ b/csharp/Lib/Units/ReactivePower.cs @@ -3,7 +3,7 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -[Sum] +[Generate] public readonly partial struct ReactivePower { public static String Unit => "var"; diff --git a/csharp/Lib/Units/ReactivePower.generated.cs b/csharp/Lib/Units/ReactivePower.generated.cs index f6b694b7a..8e094a9a5 100644 --- a/csharp/Lib/Units/ReactivePower.generated.cs +++ b/csharp/Lib/Units/ReactivePower.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct ReactivePower public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Resistance.cs b/csharp/Lib/Units/Resistance.cs index ab0bc7bbe..cd12e11a5 100644 --- a/csharp/Lib/Units/Resistance.cs +++ b/csharp/Lib/Units/Resistance.cs @@ -3,9 +3,8 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -// TODO: op parallel is wrong -[Sum] +[Generate] public readonly partial struct Resistance { public static String Unit => "Ω"; diff --git a/csharp/Lib/Units/Resistance.generated.cs b/csharp/Lib/Units/Resistance.generated.cs index 6918e60dd..38318e86c 100644 --- a/csharp/Lib/Units/Resistance.generated.cs +++ b/csharp/Lib/Units/Resistance.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Sum +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Resistance public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/State.cs b/csharp/Lib/Units/State.cs deleted file mode 100644 index 88610517c..000000000 --- a/csharp/Lib/Units/State.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections; - -namespace InnovEnergy.Lib.Units; - -public readonly struct State : IReadOnlyList -{ - public IReadOnlyList Values { get; } - - public State(IReadOnlyList values) - { - if (values.Any(v => v.Contains(";"))) - throw new ArgumentException("State values cannot contain the character ;", nameof(values)); - - Values = values; - } - - public State(params String[] values) : this((IReadOnlyList)values){} - public State(params State[] states) : this((IReadOnlyList)states.SelectMany(s => s.Values).ToList()){} - - public static implicit operator State(String s) => new(s); - public static implicit operator State(Enum e) => new(e.ToString()); - public static implicit operator State(Boolean s) => new(s.ToString()); - public static implicit operator State(List s) => new((IReadOnlyList)s); - public static implicit operator State(String[] s) => new((IReadOnlyList)s); - public static implicit operator State(List es) => new(es.Select(e => e.ToString()).ToList()); - public static implicit operator State(Enum[] es) => new(es.Select(e => e.ToString()).ToList()); - - public static State operator |(State left, State right) => new(left, right); - - public IEnumerator GetEnumerator() => Values.GetEnumerator(); - - public override String ToString() => String.Join("; ", Values); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public Int32 Count => Values.Count; - public String this[Int32 index] => Values[index]; - - public static State From(T t) where T : Enum - { - return new State(t); - } -} - - diff --git a/csharp/Lib/Units/StateOfT.cs b/csharp/Lib/Units/StateOfT.cs deleted file mode 100644 index 4a1242dc2..000000000 --- a/csharp/Lib/Units/StateOfT.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections; - -namespace InnovEnergy.Lib.Units; - -public readonly struct State : IReadOnlyList where T:Enum -{ - public IReadOnlyList Values { get; } - - public State(IReadOnlyList values) => Values = values; - - public State(params T[] values) : this((IReadOnlyList)values){} - public State(params State[] states) : this((IReadOnlyList)states.SelectMany(s => s.Values).ToList()){} - - public static implicit operator State(T s) => new((IReadOnlyList)s); - public static implicit operator State(List s) => new((IReadOnlyList)s); - public static implicit operator State(T[] s) => new((IReadOnlyList)s); - - public static State operator |(State left, State right) => new State(left, right); - - public IEnumerator GetEnumerator() => Values.GetEnumerator(); - - public override String ToString() => String.Join("; ", Values); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public Int32 Count => Values.Count; - public T this[Int32 index] => Values[index]; -} \ No newline at end of file diff --git a/csharp/Lib/Units/Temperature.cs b/csharp/Lib/Units/Temperature.cs index 57e2406a3..24f91a8a8 100644 --- a/csharp/Lib/Units/Temperature.cs +++ b/csharp/Lib/Units/Temperature.cs @@ -2,14 +2,14 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -using T=Temperature; +using T = Temperature; -[Mean] + +[Generate] public readonly partial struct Temperature { public static String Unit => "°C"; public static String Symbol => "T"; public Temperature(Decimal value) => Value = value; - } \ No newline at end of file diff --git a/csharp/Lib/Units/Temperature.generated.cs b/csharp/Lib/Units/Temperature.generated.cs index c182f6737..fc61edafa 100644 --- a/csharp/Lib/Units/Temperature.generated.cs +++ b/csharp/Lib/Units/Temperature.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Mean +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Temperature public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; diff --git a/csharp/Lib/Units/Units.cs b/csharp/Lib/Units/Units.cs index 0ee78882b..3694c0783 100644 --- a/csharp/Lib/Units/Units.cs +++ b/csharp/Lib/Units/Units.cs @@ -21,18 +21,14 @@ public static class Units public static Byte DisplaySignificantDigits { get; set; } = 3; public static Byte JsonSignificantDigits { get; set; } = 3; - - public const Decimal MaxRelativeError = 0.05m; // 5% - - public static Current A (this Decimal value) => new Current(value); - public static Voltage V (this Decimal value) => new Voltage(value); - public static Power W (this Decimal value) => new Power(value); - public static ReactivePower Var (this Decimal value) => new ReactivePower(value); - public static ApparentPower Va (this Decimal value) => new ApparentPower(value); - public static Resistance Ohm (this Decimal value) => new Resistance(value); - public static Frequency Hz (this Decimal value) => new Frequency(value); - public static Angle Rad (this Decimal value) => new Angle(value); - public static Temperature Celsius(this Decimal value) => new Temperature(value); - - + public static Current A (this Decimal value) => value; + public static Voltage V (this Decimal value) => value; + public static Power W (this Decimal value) => value; + public static ReactivePower Var (this Decimal value) => value; + public static ApparentPower Va (this Decimal value) => value; + public static Resistance Ohm (this Decimal value) => value; + public static Frequency Hz (this Decimal value) => value; + public static Angle Rad (this Decimal value) => value; + public static Temperature Celsius(this Decimal value) => value; + public static Energy KWh (this Decimal value) => value; } \ No newline at end of file diff --git a/csharp/Lib/Units/Voltage.cs b/csharp/Lib/Units/Voltage.cs index 9d9df166c..5849a2e56 100644 --- a/csharp/Lib/Units/Voltage.cs +++ b/csharp/Lib/Units/Voltage.cs @@ -2,7 +2,8 @@ using InnovEnergy.Lib.Units.Generator; namespace InnovEnergy.Lib.Units; -[Equal] + +[Generate] public readonly partial struct Voltage { public static String Unit => "V"; diff --git a/csharp/Lib/Units/Voltage.generated.cs b/csharp/Lib/Units/Voltage.generated.cs index 4f5c57f91..56ee32836 100644 --- a/csharp/Lib/Units/Voltage.generated.cs +++ b/csharp/Lib/Units/Voltage.generated.cs @@ -1,5 +1,5 @@ #nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. -#define Equal +#define Generate using static System.Math; using System.Text.Json; @@ -22,40 +22,12 @@ public readonly partial struct Voltage public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); - // parallel + // addition - #if Sum + public static T operator +(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T left, T right) => new T(left.Value - right.Value); + public static T operator -(T t) => new T(-t.Value); - public static T operator |(T left, T right) => new T(left.Value + right.Value); - public static T operator -(T t) => new T(-t.Value); - - #elif Mean - - public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); - - #elif Equal - - public static T operator |(T left, T right) - { - var d = Max(Abs(left.Value), Abs(right.Value)); - - if (d == 0m) - return new T(0m); - - var relativeError = Abs(left.Value - right.Value) / d; - - const Decimal maxRelativeError = 0.05m; - - if (relativeError > maxRelativeError) - throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + - $"Difference > {maxRelativeError * 100}% detected\n" + - $"{nameof(left)} : {left}\n" + - $"{nameof(right)}: {right}"); - - return new T((left.Value + right.Value) / 2m); - } - #endif - // compare public static Boolean operator ==(T left, T right) => left.Value == right.Value; From 17c2a959e6c997ac122cf2558afee98d4d1e5081 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 13:25:19 +0100 Subject: [PATCH 03/13] introduce implicit converters for UnixTime & UnixTimeSpan --- csharp/Lib/Time/Unix/UnixTime.Converters.cs | 3 +++ csharp/Lib/Time/Unix/UnixTimeSpan.Converters.cs | 9 +++++++++ csharp/Lib/Time/Unix/UnixTimeSpan.cs | 5 +---- csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 csharp/Lib/Time/Unix/UnixTimeSpan.Converters.cs diff --git a/csharp/Lib/Time/Unix/UnixTime.Converters.cs b/csharp/Lib/Time/Unix/UnixTime.Converters.cs index 0d5d17aeb..7c28860a9 100644 --- a/csharp/Lib/Time/Unix/UnixTime.Converters.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Converters.cs @@ -3,4 +3,7 @@ namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { public DateTime ToUtcDateTime() => DateTime.UnixEpoch + TimeSpan.FromSeconds(Ticks); + + public static implicit operator DateTime(UnixTime unixTimeSpan) => unixTimeSpan.ToUtcDateTime(); + public static implicit operator UnixTime(DateTime dateTime) => FromUtcDateTime(dateTime); } \ No newline at end of file diff --git a/csharp/Lib/Time/Unix/UnixTimeSpan.Converters.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Converters.cs new file mode 100644 index 000000000..b2bce89d4 --- /dev/null +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Converters.cs @@ -0,0 +1,9 @@ +namespace InnovEnergy.Lib.Time.Unix; + +public readonly partial struct UnixTimeSpan +{ + public TimeSpan ToTimeSpan() => TimeSpan.FromSeconds(Ticks); + + public static implicit operator TimeSpan(UnixTimeSpan unixTimeSpan) => unixTimeSpan.ToTimeSpan(); + public static implicit operator UnixTimeSpan(TimeSpan timeSpan) => FromTimeSpan(timeSpan); +} \ No newline at end of file diff --git a/csharp/Lib/Time/Unix/UnixTimeSpan.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.cs index d525deaba..4dd19d675 100644 --- a/csharp/Lib/Time/Unix/UnixTimeSpan.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.cs @@ -4,8 +4,5 @@ public readonly partial struct UnixTimeSpan { public UInt32 Ticks { get; } - public TimeSpan ToTimeSpan() - { - return TimeSpan.FromSeconds(Ticks); - } + } \ No newline at end of file diff --git a/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs b/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs index fd98e3487..625253a88 100644 --- a/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs @@ -1,6 +1,6 @@ namespace InnovEnergy.Lib.Time.Unix; -public static class UnixTimeDeltaExtensions +public static class UnixTimeSpanExtensions { public static UnixTimeSpan Seconds(this Int32 s) => UnixTimeSpan.FromSeconds(s); public static UnixTimeSpan Minutes(this Int32 m) => UnixTimeSpan.FromMinutes(m); From 249254e2e56b2b309ec05314b6f8905c07218944 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 13:27:15 +0100 Subject: [PATCH 04/13] add GeneratedCode attribute --- csharp/Lib/Units/Angle.generated.cs | 2 ++ csharp/Lib/Units/ApparentPower.generated.cs | 2 ++ csharp/Lib/Units/Current.generated.cs | 2 ++ csharp/Lib/Units/Energy.generated.cs | 2 ++ csharp/Lib/Units/Frequency.generated.cs | 2 ++ csharp/Lib/Units/Generator/Template.txt | 2 ++ csharp/Lib/Units/Power.generated.cs | 2 ++ csharp/Lib/Units/ReactivePower.generated.cs | 2 ++ csharp/Lib/Units/Resistance.generated.cs | 2 ++ csharp/Lib/Units/Temperature.generated.cs | 2 ++ csharp/Lib/Units/Voltage.generated.cs | 2 ++ 11 files changed, 22 insertions(+) diff --git a/csharp/Lib/Units/Angle.generated.cs b/csharp/Lib/Units/Angle.generated.cs index 75f16b601..ccc8722f8 100644 --- a/csharp/Lib/Units/Angle.generated.cs +++ b/csharp/Lib/Units/Angle.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Angle; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(AngleConverter))] public readonly partial struct Angle { diff --git a/csharp/Lib/Units/ApparentPower.generated.cs b/csharp/Lib/Units/ApparentPower.generated.cs index 7f5a9b9c5..68d418277 100644 --- a/csharp/Lib/Units/ApparentPower.generated.cs +++ b/csharp/Lib/Units/ApparentPower.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = ApparentPower; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(ApparentPowerConverter))] public readonly partial struct ApparentPower { diff --git a/csharp/Lib/Units/Current.generated.cs b/csharp/Lib/Units/Current.generated.cs index de5242e57..b8b19963c 100644 --- a/csharp/Lib/Units/Current.generated.cs +++ b/csharp/Lib/Units/Current.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Current; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(CurrentConverter))] public readonly partial struct Current { diff --git a/csharp/Lib/Units/Energy.generated.cs b/csharp/Lib/Units/Energy.generated.cs index f0713a828..dabe6ed16 100644 --- a/csharp/Lib/Units/Energy.generated.cs +++ b/csharp/Lib/Units/Energy.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Energy; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(EnergyConverter))] public readonly partial struct Energy { diff --git a/csharp/Lib/Units/Frequency.generated.cs b/csharp/Lib/Units/Frequency.generated.cs index fead5a326..6bdfb8ded 100644 --- a/csharp/Lib/Units/Frequency.generated.cs +++ b/csharp/Lib/Units/Frequency.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Frequency; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(FrequencyConverter))] public readonly partial struct Frequency { diff --git a/csharp/Lib/Units/Generator/Template.txt b/csharp/Lib/Units/Generator/Template.txt index 01f7220d4..c23e0820a 100644 --- a/csharp/Lib/Units/Generator/Template.txt +++ b/csharp/Lib/Units/Generator/Template.txt @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Template; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(TemplateConverter))] public readonly partial struct Template { diff --git a/csharp/Lib/Units/Power.generated.cs b/csharp/Lib/Units/Power.generated.cs index dc8ff2446..750318c6a 100644 --- a/csharp/Lib/Units/Power.generated.cs +++ b/csharp/Lib/Units/Power.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Power; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(PowerConverter))] public readonly partial struct Power { diff --git a/csharp/Lib/Units/ReactivePower.generated.cs b/csharp/Lib/Units/ReactivePower.generated.cs index 8e094a9a5..ec3be808a 100644 --- a/csharp/Lib/Units/ReactivePower.generated.cs +++ b/csharp/Lib/Units/ReactivePower.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = ReactivePower; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(ReactivePowerConverter))] public readonly partial struct ReactivePower { diff --git a/csharp/Lib/Units/Resistance.generated.cs b/csharp/Lib/Units/Resistance.generated.cs index 38318e86c..611c6a8fa 100644 --- a/csharp/Lib/Units/Resistance.generated.cs +++ b/csharp/Lib/Units/Resistance.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Resistance; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(ResistanceConverter))] public readonly partial struct Resistance { diff --git a/csharp/Lib/Units/Temperature.generated.cs b/csharp/Lib/Units/Temperature.generated.cs index fc61edafa..0e993ec15 100644 --- a/csharp/Lib/Units/Temperature.generated.cs +++ b/csharp/Lib/Units/Temperature.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Temperature; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(TemperatureConverter))] public readonly partial struct Temperature { diff --git a/csharp/Lib/Units/Voltage.generated.cs b/csharp/Lib/Units/Voltage.generated.cs index 56ee32836..42d02b6fc 100644 --- a/csharp/Lib/Units/Voltage.generated.cs +++ b/csharp/Lib/Units/Voltage.generated.cs @@ -5,11 +5,13 @@ using static System.Math; using System.Text.Json; using System.Text.Json.Serialization; using InnovEnergy.Lib.Utils; +using System.CodeDom.Compiler; namespace InnovEnergy.Lib.Units; using T = Voltage; +[GeneratedCode("generate.sh", "1")] [JsonConverter(typeof(VoltageConverter))] public readonly partial struct Voltage { From e70ab41bc0fcbe109d0726214455440f269fe9d2 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 13:28:52 +0100 Subject: [PATCH 05/13] backslashes are the worstest --- csharp/App/Backend/Backend.csproj | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj index a63522893..f990e5c8a 100644 --- a/csharp/App/Backend/Backend.csproj +++ b/csharp/App/Backend/Backend.csproj @@ -22,21 +22,16 @@ - - - - - - + PreserveNewest From 19633ec54f80b83f562fefbbaa67527e8e221842 Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 13:40:31 +0100 Subject: [PATCH 06/13] aggressively trim SysTools (mostly replaced by CliWrap) --- .../SysTools/Edges/RemoteCommandToProcess.cs | 50 --- .../Edges/RemotePathToRemoteCommand.cs | 57 ---- .../SysTools/Edges/SshHostToRemoteCommand.cs | 11 - .../Lib/SysTools/Edges/SshHostToRemotePath.cs | 11 - csharp/Lib/SysTools/Edges/StringToCommand.cs | 56 ---- csharp/Lib/SysTools/Edges/StringToProcess.cs | 26 -- .../Lib/SysTools/Edges/StringToRemotePath.cs | 8 - csharp/Lib/SysTools/Edges/StringToSysPath.cs | 6 - .../Lib/SysTools/Edges/SysCommandToProcess.cs | 72 ----- .../Edges/SysCommandToRemoteCommand.cs | 12 - csharp/Lib/SysTools/Edges/SysPathToProcess.cs | 21 -- .../Lib/SysTools/Edges/SysPathToRemotePath.cs | 11 - .../Lib/SysTools/Edges/SysPathToSysCommand.cs | 55 ---- csharp/Lib/SysTools/FileIo.cs | 5 +- csharp/Lib/SysTools/Process/AsyncProcess.cs | 186 ----------- csharp/Lib/SysTools/Process/ProcessResult.cs | 63 ---- csharp/Lib/SysTools/Process/SyncProcess.cs | 284 ----------------- csharp/Lib/SysTools/Remote/RemoteCommand.cs | 117 ------- csharp/Lib/SysTools/Remote/RemoteFileIo.cs | 299 ------------------ csharp/Lib/SysTools/Remote/RemotePath.cs | 99 ------ csharp/Lib/SysTools/Remote/SshHost.cs | 103 ------ csharp/Lib/SysTools/SysCommand.cs | 104 ------ csharp/Lib/SysTools/SysDirs.cs | 17 +- csharp/Lib/SysTools/SysPath.cs | 28 +- csharp/Lib/SysTools/Utils/ConsoleUtils.cs | 74 ----- csharp/Lib/SysTools/Utils/EnumerableUtils.cs | 87 ----- csharp/Lib/SysTools/Utils/StringUtils.cs | 208 ------------ csharp/Lib/SysTools/Utils/Utils.cs | 37 --- 28 files changed, 17 insertions(+), 2090 deletions(-) delete mode 100644 csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs delete mode 100644 csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs delete mode 100644 csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs delete mode 100644 csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs delete mode 100644 csharp/Lib/SysTools/Edges/StringToCommand.cs delete mode 100644 csharp/Lib/SysTools/Edges/StringToProcess.cs delete mode 100644 csharp/Lib/SysTools/Edges/StringToRemotePath.cs delete mode 100644 csharp/Lib/SysTools/Edges/StringToSysPath.cs delete mode 100644 csharp/Lib/SysTools/Edges/SysCommandToProcess.cs delete mode 100644 csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs delete mode 100644 csharp/Lib/SysTools/Edges/SysPathToProcess.cs delete mode 100644 csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs delete mode 100644 csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs delete mode 100644 csharp/Lib/SysTools/Process/AsyncProcess.cs delete mode 100644 csharp/Lib/SysTools/Process/ProcessResult.cs delete mode 100644 csharp/Lib/SysTools/Process/SyncProcess.cs delete mode 100644 csharp/Lib/SysTools/Remote/RemoteCommand.cs delete mode 100644 csharp/Lib/SysTools/Remote/RemoteFileIo.cs delete mode 100644 csharp/Lib/SysTools/Remote/RemotePath.cs delete mode 100644 csharp/Lib/SysTools/Remote/SshHost.cs delete mode 100644 csharp/Lib/SysTools/SysCommand.cs delete mode 100644 csharp/Lib/SysTools/Utils/ConsoleUtils.cs delete mode 100644 csharp/Lib/SysTools/Utils/EnumerableUtils.cs delete mode 100644 csharp/Lib/SysTools/Utils/StringUtils.cs delete mode 100644 csharp/Lib/SysTools/Utils/Utils.cs diff --git a/csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs b/csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs deleted file mode 100644 index 984594e11..000000000 --- a/csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs +++ /dev/null @@ -1,50 +0,0 @@ -using InnovEnergy.Lib.SysTools.Process; -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class RemoteCommandToProcess -{ - public static ProcessResult ExecuteBlocking(this RemoteCommand command, Dictionary variables = null, Boolean logToConsole = false) - { - return command - .ToLocalCommand() - .ExecuteBlocking(variables, logToConsole); - } - - public static AsyncProcess ToAsyncProcess(this RemoteCommand command, Dictionary variables = null) - { - return command - .ToLocalCommand() - .ToAsyncProcess(variables); - } - - public static SyncProcess ToSyncProcess(this RemoteCommand command, Dictionary variables = null) - { - return command - .ToLocalCommand() - .ToSyncProcess(variables); - } - - private static SysCommand ToLocalCommand(this RemoteCommand command) - { - var ssh = "ssh" - .Opt("o", "ConnectTimeout=5") // TODO - .Opt("o", "PasswordAuthentication=no") - .Opt("o", "StrictHostKeyChecking=no") - .Opt("o", "UserKnownHostsFile=/dev/null") - .Opt("o", "LogLevel=ERROR"); - - var host = command.Host; - - if (host.KeyFile.HasValue) - ssh = ssh.Opt("i", host.KeyFile.Value); - - if (host.Port != 22) - ssh = ssh.Opt("p", host.Port); - - return ssh - .Arg($"{host.User}@{host.Address}") - .Arg(command.Path + command.Args); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs deleted file mode 100644 index 5e882d473..000000000 --- a/csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class RemotePathToRemoteCommand -{ - public static RemoteCommand ToCommand(this RemotePath remotePath) => new RemoteCommand(remotePath); - - public static RemoteCommand Opt1(this RemotePath remotePath, String option) - { - return remotePath - .ToCommand() - .Opt1(option); - } - - public static RemoteCommand Opt1(this RemotePath remotePath, String option, Object value, String separator = " ") - { - return remotePath - .ToCommand() - .Opt1(option, value, separator); - } - - public static RemoteCommand Opt2(this RemotePath remotePath, String option) - { - return remotePath - .ToCommand() - .Opt2(option); - } - - public static RemoteCommand Opt2(this RemotePath remotePath, String option, Object value, String separator = "=") - { - return remotePath - .ToCommand() - .Opt2(option, value, separator); - } - - public static RemoteCommand Opt(this RemotePath remotePath, String option) - { - return remotePath - .ToCommand() - .Opt(option); - } - - public static RemoteCommand Opt(this RemotePath remotePath, String option, Object value) - { - return remotePath - .ToCommand() - .Opt(option, value); - } - - public static RemoteCommand Arg(this RemotePath remotePath, Object argument) - { - return remotePath - .ToCommand() - .Arg(argument); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs deleted file mode 100644 index c9cfba220..000000000 --- a/csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SshHostToRemoteCommand -{ - public static RemoteCommand Command(this SshHost host, SysPath path) - { - return new RemoteCommand(host, path); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs b/csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs deleted file mode 100644 index 4833861b6..000000000 --- a/csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs +++ /dev/null @@ -1,11 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SshHostToRemotePath -{ - public static RemotePath WithPath(this SshHost host, SysPath path) - { - return new RemotePath(host, path); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/StringToCommand.cs b/csharp/Lib/SysTools/Edges/StringToCommand.cs deleted file mode 100644 index 62c595f75..000000000 --- a/csharp/Lib/SysTools/Edges/StringToCommand.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class StringToCommand -{ - public static SysCommand ToCommand(this String cmd) => new SysCommand(cmd); - - public static SysCommand Opt1(this String cmd, String option) - { - return cmd - .ToCommand() - .Opt1(option); - } - - public static SysCommand Opt1(this String cmd, String option, Object value, String separator = " ") - { - return cmd - .ToCommand() - .Opt1(option, value, separator); - } - - public static SysCommand Opt2(this String cmd, String option) - { - return cmd - .ToCommand() - .Opt2(option); - } - - public static SysCommand Opt2(this String cmd, String option, Object value, String separator = "=") - { - return cmd - .ToCommand() - .Opt2(option, value, separator); - } - - public static SysCommand Opt(this String cmd, String option) - { - return cmd - .ToCommand() - .Opt(option); - } - - public static SysCommand Opt(this String cmd, String option, Object value) - { - return cmd - .ToCommand() - .Opt(option, value); - } - - public static SysCommand Arg(this String cmd, Object argument) - { - return cmd - .ToCommand() - .Arg(argument); - } - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/StringToProcess.cs b/csharp/Lib/SysTools/Edges/StringToProcess.cs deleted file mode 100644 index e8d3c4d63..000000000 --- a/csharp/Lib/SysTools/Edges/StringToProcess.cs +++ /dev/null @@ -1,26 +0,0 @@ -using InnovEnergy.Lib.SysTools.Process; - -namespace InnovEnergy.Lib.SysTools.Edges; - -using Env = Dictionary; - - -public static class StringToProcess -{ - - public static ProcessResult ExecuteBlocking(this String command, Env variables = null, Boolean logToConsole = false) - { - return SysCommandToProcess.ExecuteBlocking(command, variables, logToConsole); - } - - public static AsyncProcess ToAsyncProcess(this String command, Env variables = null, Boolean logToConsole = false) - { - return new AsyncProcess(command, variables, logToConsole); - } - - public static SyncProcess ToSyncProcess(this String command, Env variables = null, Boolean logToConsole = false) - { - return new SyncProcess(command, variables, logToConsole); - } - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/StringToRemotePath.cs b/csharp/Lib/SysTools/Edges/StringToRemotePath.cs deleted file mode 100644 index 9a32e16b1..000000000 --- a/csharp/Lib/SysTools/Edges/StringToRemotePath.cs +++ /dev/null @@ -1,8 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class StringToRemotePath -{ - public static RemotePath At(this String path, SshHost host) => new RemotePath(host, path); -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/StringToSysPath.cs b/csharp/Lib/SysTools/Edges/StringToSysPath.cs deleted file mode 100644 index bf7a93615..000000000 --- a/csharp/Lib/SysTools/Edges/StringToSysPath.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class StringToSysPath -{ - public static SysPath ToPath(this String path) => new SysPath(path); -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SysCommandToProcess.cs b/csharp/Lib/SysTools/Edges/SysCommandToProcess.cs deleted file mode 100644 index b8b142b6f..000000000 --- a/csharp/Lib/SysTools/Edges/SysCommandToProcess.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Diagnostics; -using System.Reactive.Linq; -using System.Text; -using InnovEnergy.Lib.SysTools.Process; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SysCommandToProcess -{ - public static ProcessResult ExecuteBlocking(this SysCommand command, Dictionary variables = null, Boolean logToConsole =false) - { - var process = command.ToAsyncProcess(variables, logToConsole); - - var stdOut = new List(); - var stdErr = new List(); - var output = new StringBuilder(); - - process.StandardOut.Subscribe(o => - { - stdOut.Add(o); - output.AppendLine(o); - }); - - process.StandardErr.Subscribe(e => - { - stdErr.Add(e); - output.AppendLine(e); - }); - - process.Run(); - - - var exitCode = process.ExitCode.Wait(); - - process.Terminate(); - return new ProcessResult(exitCode, stdOut, stdErr, output.ToString()); - } - - public static AsyncProcess ToAsyncProcess(this SysCommand command, Dictionary variables = null, Boolean logToConsole = false) - { - return new AsyncProcess(command, variables, logToConsole); - } - - public static SyncProcess ToSyncProcess(this SysCommand command, Dictionary variables = null, Boolean logToConsole = false) - { - return new SyncProcess(command, variables, logToConsole); - } - - public static System.Diagnostics.Process ToConsoleProcess(this SysCommand command, Dictionary variables = null) - { - var startInfo = new ProcessStartInfo - { - RedirectStandardOutput = false, - RedirectStandardError = false, - RedirectStandardInput = false, - UseShellExecute = false, - CreateNoWindow = true, - Arguments = command.Args, - FileName = command.Path, - }; - - if (variables != null) - foreach (var kv in variables) - startInfo.EnvironmentVariables[kv.Key] = kv.Value; - - return new System.Diagnostics.Process - { - StartInfo = startInfo, - EnableRaisingEvents = true - }; - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs deleted file mode 100644 index faa77d432..000000000 --- a/csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs +++ /dev/null @@ -1,12 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SysCommandToRemoteCommand -{ - public static RemoteCommand At(this SysCommand command, SshHost host) - { - var (path, args) = command; - return new RemoteCommand(host, path, args); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SysPathToProcess.cs b/csharp/Lib/SysTools/Edges/SysPathToProcess.cs deleted file mode 100644 index 60a030fe8..000000000 --- a/csharp/Lib/SysTools/Edges/SysPathToProcess.cs +++ /dev/null @@ -1,21 +0,0 @@ -using InnovEnergy.Lib.SysTools.Process; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SysPathToProcess -{ - public static ProcessResult ExecuteBlocking(this SysPath command, Dictionary variables = null, Boolean logToConsole = false) - { - return SysCommandToProcess.ExecuteBlocking(command, variables, logToConsole); - } - - public static AsyncProcess ToAsyncProcess(this SysPath command, Dictionary variables = null, Boolean logToConsole = false) - { - return new AsyncProcess(command, variables, logToConsole); - } - - public static SyncProcess ToSyncProcess(this SysPath command, Dictionary variables = null, Boolean logToConsole = false) - { - return new SyncProcess(command, variables, logToConsole); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs b/csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs deleted file mode 100644 index 410f6bb7e..000000000 --- a/csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs +++ /dev/null @@ -1,11 +0,0 @@ -using InnovEnergy.Lib.SysTools.Remote; - -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SysPathToRemotePath -{ - public static RemotePath At(this SysPath path, SshHost host) - { - return new RemotePath(host, path); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs b/csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs deleted file mode 100644 index 9f27e68ff..000000000 --- a/csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Edges; - -public static class SysPathToSysCommand -{ - public static SysCommand ToCommand(this SysPath path) => new SysCommand(path); - - public static SysCommand Opt1(this SysPath path, String option) - { - return path - .ToCommand() - .Opt1(option); - } - - public static SysCommand Opt1(this SysPath path, String option, Object value, String separator = " ") - { - return path - .ToCommand() - .Opt1(option, value, separator); - } - - public static SysCommand Opt2(this SysPath path, String option) - { - return path - .ToCommand() - .Opt2(option); - } - - public static SysCommand Opt2(this SysPath path, String option, Object value, String separator = "=") - { - return path - .ToCommand() - .Opt2(option, value, separator); - } - - public static SysCommand Opt(this SysPath path, String option) - { - return path - .ToCommand() - .Opt(option); - } - - public static SysCommand Opt(this SysPath path, String option, Object value) - { - return path - .ToCommand() - .Opt(option, value); - } - - public static SysCommand Arg(this SysPath path, Object argument) - { - return path - .ToCommand() - .Arg(argument); - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/FileIo.cs b/csharp/Lib/SysTools/FileIo.cs index bec888e56..553ef1418 100644 --- a/csharp/Lib/SysTools/FileIo.cs +++ b/csharp/Lib/SysTools/FileIo.cs @@ -3,7 +3,6 @@ using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.SysTools; -[Obsolete("Needs rework before use")] public static class FileIo { public static Boolean Exists (this SysPath path) => path.FileExists() || path.DirectoryExists(); @@ -92,7 +91,7 @@ public static class FileIo SysPath Target(SysPath path) => targetDir.Append(path.RelativeTo(sourceDir)); - sourceDir.Traverse(Directories) + sourceDir.TraverseDepthFirstPreOrder(Directories) .Do(d => Target(d).CreateDirectory()) .SelectMany(Files) .ForEach(f => f.CopyFileTo(Target(f))); @@ -116,7 +115,7 @@ public static class FileIo public static IEnumerable DescendantDirectories(this SysPath path) { - return path.Traverse(Directories); + return path.TraverseDepthFirstPreOrder(Directories); } public static IEnumerable DescendantFiles(this SysPath path) diff --git a/csharp/Lib/SysTools/Process/AsyncProcess.cs b/csharp/Lib/SysTools/Process/AsyncProcess.cs deleted file mode 100644 index ee0137482..000000000 --- a/csharp/Lib/SysTools/Process/AsyncProcess.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System.Diagnostics; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using InnovEnergy.Lib.SysTools.Utils; -using static System.ConsoleColor; - -namespace InnovEnergy.Lib.SysTools.Process; - -using Env = Dictionary; - -[Obsolete("Use CliWrap instead")] -public class AsyncProcess -{ - private readonly Subject _StandardIn; - private readonly Subject _StandardOut; - private readonly Subject _StandardErr; - private readonly ReplaySubject _ExitCode; - - public SysCommand Command { get; } - public Env Environment { get; } - - public IObserver StandardIn => _StandardIn; - public IObservable StandardOut => _StandardOut; - public IObservable StandardErr => _StandardErr; - public IObservable ExitCode => _ExitCode; - - public Boolean HasStarted => Process != null; - public Boolean HasFinished => HasStarted && Process.HasExited; - - private System.Diagnostics.Process Process { get; set; } - - public AsyncProcess(SysCommand command, Env environment = null, Boolean logToConsole = false) - { - Command = command; - Environment = environment; - - _StandardIn = new Subject(); - _StandardOut = new Subject(); - _StandardErr = new Subject(); - _ExitCode = new ReplaySubject(); - - if (logToConsole) - LogToConsole(); - } - - private void LogToConsole() - { - _StandardOut.Subscribe(d => d.WriteLine()); - _StandardErr.Subscribe(d => d.WriteLine(Red)); - _ExitCode.Subscribe(ec => - { - "ExitCode: ".Write(Cyan); - ec.WriteLine(ec == 0 ? Green : Red); - }); - } - - - public Int32 WaitForExit() - { - EnsureStarted(); - - return ExitCode.Wait(); - - //Process.WaitForExit(); // TODO - //return Process.ExitCode; - } - - private void EnsureNotStarted() - { - if (HasStarted) - throw new Exception("Process has already started"); - } - - private void EnsureStarted() - { - if (!HasStarted) - throw new Exception("Process has not yet started"); - } - - - public AsyncProcess Run() - { - EnsureNotStarted(); - Process = InitProcess(); - - return this; - } - - private System.Diagnostics.Process InitProcess() - { - var startInfo = new ProcessStartInfo - { - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - Arguments = Command.Args, - FileName = Command.Path, - }; - - foreach (var ev in Environment.EmptyIfNull()) - startInfo.EnvironmentVariables[ev.Key] = ev.Value; - - var proc = new System.Diagnostics.Process - { - StartInfo = startInfo, - EnableRaisingEvents = true - }; - - proc.Start(); // must start BEFORE we access Streams - - Observable.Repeat(proc.StandardOutput, TaskPoolScheduler.Default) - .Select(s => s.ReadLine()) - .TakeWhile(l => l != null) - .Subscribe(_StandardOut); - - Observable.Repeat(proc.StandardError, TaskPoolScheduler.Default) - .Select(s => s.ReadLine()) - .TakeWhile(l => l != null) - .Subscribe(_StandardErr); - - _StandardIn.Subscribe(onNext: proc.StandardInput.WriteLine, - onError: _ => proc.StandardInput.Close(), - onCompleted: proc.StandardInput.Close); - - Observable.FromEventPattern(e => proc.Exited += e, e => proc.Exited -= e) - .Take(1) - .Do(_=>Console.WriteLine("Exited")) - .OfType() - .Concat(StandardOut.IgnoreElements()) // make sure std streams finish first - .Concat(StandardErr.IgnoreElements()) - .Select(_ => Process.ExitCode) - .Subscribe(_ExitCode); - - _ExitCode.Subscribe(_ => - { - proc.StandardInput.Close(); - proc.StandardOutput.Close(); - proc.StandardError.Close(); - }); - - return proc; - } - - public AsyncProcess Terminate() - { - - try - { - Process.Kill(); - } - catch - { - // ignored - } - - - return this; - } - - public AsyncProcess Kill() - { - EnsureStarted(); - - try - { - Process.Kill(); - } - catch - { - // ignored - } - - return this; - } - - - public IDisposable ThrowOnStdErr() - { - return StandardErr.Subscribe(e => throw new Exception(e)); - } - - public override String ToString() => Command.ToString(); // TODO: env -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Process/ProcessResult.cs b/csharp/Lib/SysTools/Process/ProcessResult.cs deleted file mode 100644 index e8af589b3..000000000 --- a/csharp/Lib/SysTools/Process/ProcessResult.cs +++ /dev/null @@ -1,63 +0,0 @@ -using InnovEnergy.Lib.SysTools.Utils; - -namespace InnovEnergy.Lib.SysTools.Process; - -[Obsolete("Use CliWrap instead")] -public readonly struct ProcessResult -{ - public ProcessResult(Int32 exitCode, - IReadOnlyList stdOut, - IReadOnlyList stdErr, - String output) - { - ExitCode = exitCode; - StdOut = stdOut; - StdErr = stdErr; - Output = output; - } - - public Int32 ExitCode { get; } - public String Output { get; } - - public IReadOnlyList StdOut { get; } - public IReadOnlyList StdErr { get; } - - public ProcessResult ThrowOnError() - { - if (ExitCode != 0) - { - if (StdErr.Count ==0) - throw new Exception(nameof(AsyncProcess) + " exited with exit code " + ExitCode); - - throw new Exception(StdErr.Aggregate((a, b) => a.NewLine() + b)); - } - - return this; - } - - public ProcessResult OnError(Action action) - { - if (Failed) action(this); - return this; - } - - public ProcessResult OnSuccess(Action action) - { - if (Succeeded) action(this); - return this; - } - - public ProcessResult Dump() - { - Output.Write(); - return this; - } - - public Boolean Succeeded => ExitCode == 0; - public Boolean Failed => ExitCode != 0; - - public override String ToString() - { - return Output.NewLine() + "ExitCode: " + ExitCode; - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Process/SyncProcess.cs b/csharp/Lib/SysTools/Process/SyncProcess.cs deleted file mode 100644 index 7d47f447f..000000000 --- a/csharp/Lib/SysTools/Process/SyncProcess.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Diagnostics; -using System.Text.RegularExpressions; -using InnovEnergy.Lib.SysTools.Utils; -using static System.ConsoleColor; - -namespace InnovEnergy.Lib.SysTools.Process; - -using Env = Dictionary; - -[Obsolete("Use CliWrap instead")] -public class SyncProcess -{ - public SysCommand Command { get; } - public Env Environment { get; } - public Boolean LogToConsole { get; } - - public Boolean HasFinished => Process.HasExited; - - private Task<(DateTime timeStamp, StdOutput output)> _ReadStdOut; - private Task<(DateTime timeStamp, StdError error )> _ReadStdErr; - - private System.Diagnostics.Process Process { get; } - - public SyncProcess(SysCommand command, Env environment = null, Boolean logToConsole = false) - { - Command = command; - Environment = environment; - LogToConsole = logToConsole; - Process = InitProcess(); - - _ReadStdOut = ReadNextStdOut(); - _ReadStdErr = ReadNextStdErr(); - } - - private Task<(DateTime timeStamp, StdOutput output)> ReadNextStdOut() - { - Debug.Assert(_ReadStdOut == null || _ReadStdOut.IsCompleted); - - return Process - .StandardOutput - .ReadLineAsync() - .ContinueWith(t => (DateTime.Now, SyncProcessOutput.StdOutput(t.Result))); - - } - - private Task<(DateTime timeStamp, StdError error)> ReadNextStdErr() - { - Debug.Assert(_ReadStdErr == null || _ReadStdErr.IsCompleted); - - return Process - .StandardError - .ReadLineAsync() - .ContinueWith(t => (DateTime.Now, SyncProcessOutput.StdError(t.Result))); - } - - - private System.Diagnostics.Process InitProcess() - { - var startInfo = new ProcessStartInfo - { - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - Arguments = Command.Args.Trim(), - FileName = Command.Path, - }; - - foreach (var ev in Environment.EmptyIfNull()) - startInfo.EnvironmentVariables[ev.Key] = ev.Value; - - var proc = new System.Diagnostics.Process - { - StartInfo = startInfo, - EnableRaisingEvents = true - }; - - proc.Start(); // must start BEFORE we access Streams - - return proc; - } - - public Match ReadStdOutUntil(Regex rx) - { - foreach (var l in ReadStdOut()) - { - - var match = rx.Match(l); - if (match.Success) - return match; - } - - throw new Exception("no matching output"); - } - - public String ReadStdOutUntil(String line) - { - return ReadStdOutUntil(l => l == line); - } - - - public String ReadStdOutUntil(Func predicate) - { - foreach (var l in ReadStdOut()) - { - if (predicate(l)) - return l; - } - - throw new Exception("no matching output"); - } - - public IEnumerable ReadStdOut(TimeSpan timeOut = default) => ReadMany(timeOut).ThrownOnStdErr(); - - public IEnumerable ReadMany(TimeSpan timeOut = default) - { - SyncProcessOutput output; - - do - { - output = ReadNext(timeOut); - yield return output; - } - while (!(output is ExitCode) && !(output is TimeoutError)); - } - - public SyncProcessOutput ReadNext(TimeSpan timeOut = default) - { - var wait = timeOut == default - ? Task.WaitAny(_ReadStdOut, _ReadStdErr) - : Task.WaitAny(_ReadStdOut, _ReadStdErr, Task.Delay(timeOut)); - - if (wait == 2) - { - if (LogToConsole) - "Timeout".WriteLine(Red); - - return SyncProcessOutput.TimeoutError(timeOut); - } - - if (_ReadStdOut.IsCompleted && _ReadStdErr.IsCompleted) - { - var (outTimeStamp, output) = _ReadStdOut.Result; - var (errTimeStamp, error) = _ReadStdErr.Result; - - if (outTimeStamp < errTimeStamp && output != null) - { - _ReadStdOut = ReadNextStdOut(); - - if (LogToConsole) - output.Data.WriteLine(); - - return output; - } - - if (error != null) - { - _ReadStdErr = ReadNextStdErr(); - - if (LogToConsole) - error.Data.WriteLine(Red); - - return error; - } - - Process.WaitForExit(); - - var exitCode = Process.ExitCode; - - if (LogToConsole) - { - "ExitCode: ".Write(Cyan); - exitCode.WriteLine(exitCode == 0 ? Green : Red); - } - - return SyncProcessOutput.ExitCode(exitCode); - } - - if (_ReadStdOut.IsCompleted) - { - var (_, output) = _ReadStdOut.Result; - - if (output != null) - { - _ReadStdOut = ReadNextStdOut(); - return output; - } - - } - - if (_ReadStdErr.IsCompleted) - { - var (_, error) = _ReadStdErr.Result; - - if (error != null) - { - _ReadStdErr = ReadNextStdErr(); - return error; - } - - } - - throw new Exception("This should not happen"); - } - - public SyncProcess Terminate() - { - Process.Kill(); - return this; - } - - public SyncProcess Kill() - { - Process.Kill(); - return this; - } - - public void Write(String line) - { - Process.StandardInput.WriteLine(line); - } - - public override String ToString() => Command.ToString(); // TODO: env -} - - -public abstract class SyncProcessOutput -{ - public static StdOutput StdOutput (String data ) => new StdOutput(data); - public static StdError StdError (String data ) => new StdError(data); - public static ExitCode ExitCode (Int32 data ) => new ExitCode(data); - public static TimeoutError TimeoutError (TimeSpan timeout) => new TimeoutError(timeout); -} - -public class ExitCode : SyncProcessOutput -{ - public ExitCode(Int32 data) { Data = data;} - public Int32 Data { get; } - - public override String ToString() => Data.ToString(); -} - -public class StdError : SyncProcessOutput -{ - public StdError(String data) { Data = data;} - public String Data { get; } - - public override String ToString() => Data; -} - -public class StdOutput : SyncProcessOutput -{ - public StdOutput(String data) { Data = data; } - public String Data { get; } - - public override String ToString() => Data; -} - -public class TimeoutError : SyncProcessOutput -{ - public TimeoutError(TimeSpan timeout) { Timeout = timeout; } - public TimeSpan Timeout { get; } - - public override String ToString() => Timeout.ToString(); -} - - -public static class ProcessOutputExtensions -{ - public static IEnumerable ThrownOnStdErr(this IEnumerable po) - { - foreach (var p in po) - { - if (p is StdOutput o) - yield return o.Data; - - if (p is StdError e) - throw new Exception(e.Data); - - // Ignore exit code - } - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Remote/RemoteCommand.cs b/csharp/Lib/SysTools/Remote/RemoteCommand.cs deleted file mode 100644 index fa80d2671..000000000 --- a/csharp/Lib/SysTools/Remote/RemoteCommand.cs +++ /dev/null @@ -1,117 +0,0 @@ -using InnovEnergy.Lib.SysTools.Utils; - -namespace InnovEnergy.Lib.SysTools.Remote; - -[Obsolete("Use CliWrap instead")] -public readonly struct RemoteCommand -{ - public SshHost Host { get; } - public SysPath Path { get; } - public String Args { get; } - - public RemoteCommand(RemotePath remotePath, String args = "") - { - var (host, path) = remotePath; - - Args = args; - Host = host; - Path = path; - } - - public RemoteCommand(SshHost host, SysPath path, String args = "") - { - Args = args; - Host = host; - Path = path; - } - - public static RemoteCommand FromRemotePath(RemotePath remotePath) => new RemoteCommand(remotePath); - - - // TODO - public RemoteCommand Opt1(String option) - { - return new RemoteCommand(Host, Path, $"{Args} -{option}"); - } - - public RemoteCommand Opt1(String option, Object value, String separator = " ") - { - return new RemoteCommand(Host, Path, $"{Args} -{option}{separator}{Quote(value)}"); - } - - public RemoteCommand Opt2(String option) - { - return new RemoteCommand(Host, Path, $"{Args} --{option}"); - } - - public RemoteCommand Opt2(String option, Object value, String separator = "=") - { - return new RemoteCommand(Host, Path, $"{Args} --{option}{separator}{Quote(value)}"); - } - - public RemoteCommand Opt(String option) - { - return option.Length == 1 - ? Opt1(option) - : Opt2(option); - } - - public RemoteCommand Opt(String option, Object value) - { - return option.Length == 1 - ? Opt1(option, value) - : Opt2(option, value); - } - - public RemoteCommand Arg(Object argument) - { - return new RemoteCommand(Host, Path, Args + " " + Quote(argument)); - } - - private static String Quote(Object argument) - { - var arg = argument.ToString(); - if (arg.Contains(" ")) - arg = arg.Quote(); // TODO: single quote? - - return arg; - } - - - #region equality - - public Boolean Equals(RemoteCommand other) - { - return Host.Equals(other.Host) && Path.Equals(other.Path) && Args == other.Args; - } - - public override Boolean Equals(Object obj) - { - return obj is RemoteCommand other && Equals(other); - } - - public override Int32 GetHashCode() - { - unchecked - { - var hashCode = Host.GetHashCode(); - hashCode = (hashCode * 397) ^ Path.GetHashCode(); - hashCode = (hashCode * 397) ^ (Args != null ? Args.GetHashCode() : 0); - return hashCode; - } - } - - #endregion equality - - - public static implicit operator RemoteCommand(RemotePath remotePath) => new RemoteCommand(remotePath); - - public void Deconstruct(out SshHost host, out SysPath path, out String args) - { - host = Host; - path = Path; - args = Args; - } - - public override String ToString() => $"{Host}:{Path}{Args}"; -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Remote/RemoteFileIo.cs b/csharp/Lib/SysTools/Remote/RemoteFileIo.cs deleted file mode 100644 index e317d7227..000000000 --- a/csharp/Lib/SysTools/Remote/RemoteFileIo.cs +++ /dev/null @@ -1,299 +0,0 @@ -using InnovEnergy.Lib.SysTools.Edges; -using InnovEnergy.Lib.SysTools.Utils; - -namespace InnovEnergy.Lib.SysTools.Remote; - -[Obsolete("Needs rework before use")] -public static class RemoteFileIo -{ - - public static Boolean Exists(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "test" - .Opt("e") - .Arg(path) - .At(host) - .ExecuteBlocking() - .ExitCode == 0; - } - - // TODO: how to find out if ssh has failed or remote command? - - public static Boolean DirectoryExists(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "test" - .Opt("d") - .Arg(path) - .At(host) - .ExecuteBlocking() - .ExitCode == 0; - } - - public static Boolean FileExists(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "test" - .Opt("f") - .Arg(path) - .At(host) - .ExecuteBlocking() - .ExitCode == 0; - } - - public static Boolean DirectoryExists(this RemotePath remotePath, String directoryName) - { - return DirectoryExists(remotePath / directoryName); - } - - public static Boolean FileExists(this RemotePath remotePath, String fileName) - { - return DirectoryExists(remotePath / fileName); - } - - public static IEnumerable Directories(this RemotePath rp) - { - var (host, path) = rp; - - return "find" - .Arg(path) - .Opt1("mindepth", 1) - .Opt1("maxdepth", 1) - .Opt1("type", "d") - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut - .Select(p => new RemotePath(host, p)); - } - - public static IEnumerable Files(this RemotePath rp) - { - var (host, path) = rp; - - return "find" - .Arg(path) - .Opt1("mindepth", 1) - .Opt1("maxdepth", 1) - .Opt1("type", "f") - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut - .Select(p => new RemotePath(host, p)); - } - - public static RemotePath CreateDirectory(this RemotePath remotePath) - { - var (host, path) = remotePath; - - "mkdir".Arg(path) - .At(host) - .ExecuteBlocking() - .ThrowOnError(); - - return remotePath; - } - - public static RemotePath MoveFileTo(this RemotePath sourcePath, RemotePath targetPath) - { - throw new NotImplementedException(); - } - - public static RemotePath MoveDirectory(this RemotePath sourcePath, RemotePath targetPath) - { - throw new NotImplementedException(); - } - - public static RemotePath CreateDirectory(this RemotePath path, String dirName) - { - return CreateDirectory(path / dirName); - } - - public static RemotePath DeleteFile(this RemotePath remotePath) - { - var (host, path) = remotePath; // TODO: test file - - "rm".Arg(path) - .At(host) - .ExecuteBlocking() - .ThrowOnError(); - - return remotePath; - } - - - public static RemotePath DeleteFile(this RemotePath path, String fileName) - { - return DeleteFile(path / fileName); - } - - public static RemotePath DeleteDirectory(this RemotePath remotePath) - { - var (host, path) = remotePath; // TODO: test dir - - "rm".Opt("r") - .Arg(path) - .At(host) - .ExecuteBlocking() - .ThrowOnError(); - - return remotePath; - } - - public static RemotePath DeleteDirectory(this RemotePath path, String directoryName) - { - return DeleteDirectory(path / directoryName); - } - - public static RemotePath CopyDirectoryTo(this RemotePath sourceDir, RemotePath targetDir) - { - throw new NotImplementedException(); - } - - - public static RemotePath CopyFileTo(this RemotePath srcFile, RemotePath targetFile) - { - throw new NotImplementedException(); - } - - public static RemotePath CopyFileToDirectory(this RemotePath srcFile, RemotePath targetDir) - { - throw new NotImplementedException(); - } - - public static IEnumerable DescendantDirectories(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "find" - .Arg(path) - .Opt1("type", "d") - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut - .Select(d => new RemotePath(host, d)); - } - - public static IEnumerable DescendantFiles(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "find" - .Arg(path) - .Opt1("type", "f") - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut - .Select(d => new RemotePath(host, d)); - } - - public static IEnumerable Descendants(this RemotePath remotePath) - { - var (host, path) = remotePath; - return "find" - .Arg(path) - .Opt1("mindepth", 1) - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut - .Select(l => new RemotePath(host, l)); - } - - - public static IEnumerable ReadBytes(this RemotePath path) - { - throw new NotImplementedException(); - } - - - public static IReadOnlyList ReadLines(this RemotePath remotePath) - { - var (host, path) = remotePath; - - return "cat" - .Arg(path) - .At(host) - .ExecuteBlocking() - .ThrowOnError() - .StdOut; - } - - // public static IEnumerable ReadLines(this RemotePath path, Encoding encoding) - // { - // throw new NotImplementedException(); - // } - - //public static String ReadText(this RemotePath path) => path.ReadText(Encoding.UTF8); - - public static String ReadText(this RemotePath rp) - { - var lines = ReadLines(rp); - - if (lines.Count == 0) return ""; - if (lines.Count == 1) return lines[0]; - - return lines.Aggregate((a, b) => a.NewLine() + b); - } - - public static RemotePath WriteText(this RemotePath rp, String text) - { - throw new NotImplementedException(); - } - - - public static RemotePath AppendLines(this RemotePath filePath, params String[] lines) - { - return AppendLines(filePath, (IEnumerable) lines); - } - - public static RemotePath AppendLines(this RemotePath rp, IEnumerable lines) - { - return WriteLines(rp, lines, append: true); - } - - public static RemotePath WriteLines(this RemotePath filePath, params String[] lines) - { - return WriteLines(filePath, (IEnumerable) lines); - } - - public static RemotePath WriteLines(this RemotePath rp, IEnumerable lines) - { - return WriteLines(rp, lines, append: false); - } - - private static RemotePath WriteLines(this RemotePath rp, IEnumerable lines, Boolean append) - { - var (host, path) = rp; - - var proc = "cat" - .Arg(append ? ">>" : ">") - .Arg(path) - .At(host).ToAsyncProcess(); - - foreach (var line in lines) - proc.StandardIn.OnNext(line); - - proc.StandardIn.OnCompleted(); // TODO: OnCompleted or not? - - // TODO: error handling - - var exitCode = proc.WaitForExit(); - - return rp; - } - - public static RemotePath AppendText(this RemotePath filePath, String text) - { - throw new NotImplementedException(); - } - - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Remote/RemotePath.cs b/csharp/Lib/SysTools/Remote/RemotePath.cs deleted file mode 100644 index dcb75adb3..000000000 --- a/csharp/Lib/SysTools/Remote/RemotePath.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Remote; - -[Obsolete] -public readonly struct RemotePath -{ - public SysPath Path { get; } - public SshHost Host { get; } - - public RemotePath Parent => Path.Parent.At(Host); - public String Tail => Path.Tail; - public String Head => Path.Head; - public String Extension => Path.Extension; - - public RemotePath(SshHost host, SysPath path) - { - Host = host; - Path = path; - } - - public RemotePath At(SshHost host) - { - return new RemotePath(host, Path); - } - - public RemotePath ToRelative() => Path.ToRelative().At(Host); - public RemotePath ToAbsolute() => Path.ToAbsolute().At(Host); - public RemotePath ToEnvRef() => Path.ToEnvRef().At(Host); - - public RemotePath RelativeTo(RemotePath referencePath) - { - if (!Host.Equals(referencePath.Host)) - throw new ArgumentException(nameof(referencePath)); - - return Path.RelativeTo(referencePath.Path).At(Host); - } - - public RemotePath RelativeTo(SysPath referencePath) - { - return Path.RelativeTo(referencePath).At(Host); - } - - public Boolean IsSubPathOf(RemotePath referencePath) - { - var (host, path) = referencePath; - return Host.Equals(host) && Path.IsSubPathOf(path); - } - - public RemotePath Append(SysPath right) - { - return Path.Append(right).At(Host); - } - - #region equality - - public Boolean Equals(RemotePath other) - { - var (host, path) = other; - return Path.Equals(path) && Host.Equals(host); - } - - public override Boolean Equals(Object obj) - { - return obj is RemotePath other && Equals(other); - } - - public override Int32 GetHashCode() - { - unchecked - { - return (Path.GetHashCode() * 397) ^ Host.GetHashCode(); - } - } - - #endregion equality - - - - - - #region operators - - public static RemotePath operator /(RemotePath left, SysPath right) => left.Append(right); - public static RemotePath operator /(RemotePath left, String right) => left.Append(right); - - public static Boolean operator ==(RemotePath left, RemotePath right) => left.Equals(right); - public static Boolean operator !=(RemotePath left, RemotePath right) => left.Equals(right); - - public static implicit operator String(RemotePath remotePath) => remotePath.ToString(); - - #endregion - - public void Deconstruct(out SshHost host, out SysPath path) - { - path = Path; - host = Host; - } - - public override String ToString() => $"{Host}:{Path}"; -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Remote/SshHost.cs b/csharp/Lib/SysTools/Remote/SshHost.cs deleted file mode 100644 index a957f2887..000000000 --- a/csharp/Lib/SysTools/Remote/SshHost.cs +++ /dev/null @@ -1,103 +0,0 @@ -using InnovEnergy.Lib.SysTools.Edges; -using InnovEnergy.Lib.SysTools.Utils; - -namespace InnovEnergy.Lib.SysTools.Remote; - -[Obsolete("Needs rework before use")] -public readonly struct SshHost -{ - public const Int32 DefaultPort = 22; - - public String User { get; } - public String Address { get; } - public Int32 Port { get; } - public SysPath? KeyFile { get; } - - public SshHost(String user, String address, Int32 port = DefaultPort) - { - User = user; - Address = address; - KeyFile = null; - Port = port; - } - - public SshHost(String user, String address, SysPath? keyFile, Int32 port = DefaultPort) - { - User = user; - Address = address; - KeyFile = keyFile; - Port = port; - } - - public static SshHost Parse(String userAtAddressColonPort) - { - var addressPort = userAtAddressColonPort.AfterFirst('@').Split(':'); - - var user = userAtAddressColonPort.UntilFirst('@'); - var address = addressPort[0]; - var port = addressPort.Length < 2 ? DefaultPort : Int32.Parse(addressPort[1]); - - return new SshHost(user, address, null, port); - } - - public static SshHost Parse(String userAtAddressColonPort, SysPath keyFile) - { - var addressPort = userAtAddressColonPort.AfterFirst('@').Split(':'); - - var user = userAtAddressColonPort.UntilFirst('@'); - var address = addressPort[0]; - var port = addressPort.Length < 2 ? DefaultPort : Int32.Parse(addressPort[1]); - - return new SshHost(user, address, keyFile, port); - } - - - - public static SshHost Localhost { get; } = new SshHost(Environment.UserName, "127.0.0.1"); - - public Boolean IsLocalhost => Equals(Localhost); - - public override String ToString() => $"{User}@{Address}"; - - #region equality - - public Boolean Equals(SshHost other) - { - return User == other.User && - Address == other.Address && - Port == other.Port && - Nullable.Equals(KeyFile, other.KeyFile); - } - - public override Boolean Equals(Object obj) - { - return obj is SshHost other && Equals(other); - } - - public override Int32 GetHashCode() - { - unchecked - { - var hashCode = (User != null ? User.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (Address != null ? Address.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ Port; - hashCode = (hashCode * 397) ^ KeyFile.GetHashCode(); - return hashCode; - } - } - - #endregion - - public static RemotePath operator / (SshHost host, SysPath path) => host.WithPath(path); - public static RemotePath operator / (SshHost host, String path) - { - return host.WithPath(path.ToPath().ToAbsolute()); - } - - public void Deconstruct(out String user, out String address, out SysPath? keyFile) - { - user = User; - address = Address; - keyFile = KeyFile; - } -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/SysCommand.cs b/csharp/Lib/SysTools/SysCommand.cs deleted file mode 100644 index 50237a5d8..000000000 --- a/csharp/Lib/SysTools/SysCommand.cs +++ /dev/null @@ -1,104 +0,0 @@ -using InnovEnergy.Lib.SysTools.Utils; - -namespace InnovEnergy.Lib.SysTools; - -[Obsolete("Use CliWrap instead")] -public readonly struct SysCommand -{ - public SysPath Path { get; } - public String Args { get; } - - public SysCommand(SysPath path, String args = "") - { - Path = path; - Args = args; - } - - public static SysCommand FromPath(SysPath path) => new SysCommand(path); - public static SysCommand FromString(String cmd) => new SysCommand(cmd); - - public SysCommand Opt1(String option) - { - return new SysCommand(Path, $"{Args} -{option}"); - } - - public SysCommand Opt1(String option, Object value, String separator = " ") - { - return new SysCommand(Path, $"{Args} -{option}{separator}{QuoteArg(value)}"); - } - - public SysCommand Opt2(String option) - { - return new SysCommand(Path, $"{Args} --{option}"); - } - - public SysCommand Opt2(String option, Object value, String separator = "=") - { - return new SysCommand(Path, $"{Args} --{option}{separator}{QuoteArg(value)}"); - } - - public SysCommand Opt(String option) - { - return option.Length == 1 - ? Opt1(option) - : Opt2(option); - } - - public SysCommand Opt(String option, Object value) - { - return option.Length == 1 - ? Opt1(option, value) - : Opt2(option, value); - } - - public SysCommand Arg(Object argument) - { - return new SysCommand(Path, $"{Args} {QuoteArg(argument)}"); - } - - private static String QuoteArg(Object argument) - { - var arg = argument.ToString(); - if (arg.Contains(" ")) - arg = arg.Quote(); // TODO: single quote? - - return arg; - } - - public static implicit operator SysCommand(String cmd) => FromString(cmd); - public static implicit operator SysCommand(SysPath path) => FromPath(path); - - public static SysCommand operator %(SysCommand cmd, String argument) => cmd.Arg(argument); - public static SysCommand operator -(SysCommand cmd, String option ) => cmd.Opt(option); - public static SysCommand operator -(SysCommand cmd, (String opt, Object value) option ) => cmd.Opt(option.opt, option.value); - - #region equality - - public Boolean Equals(SysCommand other) - { - return Path.Equals(other.Path) && Args == other.Args; - } - - public override Boolean Equals(Object obj) - { - return obj is SysCommand other && Equals(other); - } - - public override Int32 GetHashCode() - { - unchecked - { - return (Path.GetHashCode() * 397) ^ (Args != null ? Args.GetHashCode() : 0); - } - } - - #endregion equality - - public void Deconstruct(out SysPath path, out String args) - { - path = Path; - args = Args; - } - - public override String ToString() => $"{Path}{Args}"; -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/SysDirs.cs b/csharp/Lib/SysTools/SysDirs.cs index c65d4e7b7..fd30895fa 100644 --- a/csharp/Lib/SysTools/SysDirs.cs +++ b/csharp/Lib/SysTools/SysDirs.cs @@ -6,22 +6,9 @@ namespace InnovEnergy.Lib.SysTools; [Obsolete] public static class SysDirs { - - // TODO conditional compilation - - /* - public static SysPath ProgramDirectory => ExecutingAssemblyDirectory.Parent.Head == "bin" && (ExecutingAssemblyDirectory.Head == "Debug" || ExecutingAssemblyDirectory.Head == "Release") - ? ExecutingAssemblyDirectory.Parent.Parent - : ExecutingAssemblyDirectory; - - public static SysPath ExecutingAssemblyDirectory => ExecutingAssembly.Parent; - public static SysPath ExecutingAssembly => Assembly.GetExecutingAssembly().Location.Apply(SysPath.FromUri); - */ - public static SysPath CurrentDirectory => Environment.CurrentDirectory; - public static SysPath TempDirectory => System.IO.Path.GetTempPath(); + public static SysPath CurrentDirectory => Environment.CurrentDirectory; + public static SysPath TempDirectory => Path.GetTempPath(); public static SysPath UserHomeDirectory { get; } = GetFolderPath(UserProfile); public static SysPath UserDesktopDirectory { get; } = GetFolderPath(DesktopDirectory); - - } \ No newline at end of file diff --git a/csharp/Lib/SysTools/SysPath.cs b/csharp/Lib/SysTools/SysPath.cs index b5ff40c36..1527ff378 100644 --- a/csharp/Lib/SysTools/SysPath.cs +++ b/csharp/Lib/SysTools/SysPath.cs @@ -1,11 +1,10 @@ using System.Text; -using InnovEnergy.Lib.SysTools.Remote; -using InnovEnergy.Lib.SysTools.Utils; +using InnovEnergy.Lib.Utils; using static System.IO.Path; namespace InnovEnergy.Lib.SysTools; -[Obsolete("Needs rework before use")] + public readonly struct SysPath { private readonly String _Path; @@ -28,9 +27,9 @@ public readonly struct SysPath throw new ArgumentException(nameof(path)); path = path - .Replace(@"\", "/") - .Trim() - .TrimEnd('/'); + .Replace(@"\", "/") + .Trim() + .TrimEnd('/'); if (path.Length == 0) _Path = "/"; // root else if (!path.Contains('/')) _Path = path; // env @@ -160,15 +159,15 @@ public readonly struct SysPath var path = _Path.Split(); var prefixSize = Enumerable - .Zip(refPath, path, (l, r) => l == r) - .TakeWhile(e => e) - .Count(); + .Zip(refPath, path, (l, r) => l == r) + .TakeWhile(e => e) + .Count(); var nUps = refPath.Length - prefixSize; var dirs = Enumerable - .Repeat("..", nUps) - .Concat(path.Skip(prefixSize)); + .Repeat("..", nUps) + .Concat(path.Skip(prefixSize)); var relative = String.Join(DirectorySeparatorChar.ToString(), dirs); @@ -197,13 +196,12 @@ public readonly struct SysPath return $"{_Path}/{right}"; } - public RemotePath At(SshHost host) => new RemotePath(host, this); #region overrides - public override Boolean Equals(Object obj) => _Path.Equals(obj?.ToString()); - public override Int32 GetHashCode() => _Path.GetHashCode(); - public override String ToString() => _Path; + public override Boolean Equals(Object? obj) => _Path.Equals(obj?.ToString()); + public override Int32 GetHashCode() => _Path.GetHashCode(); + public override String ToString() => _Path; #endregion diff --git a/csharp/Lib/SysTools/Utils/ConsoleUtils.cs b/csharp/Lib/SysTools/Utils/ConsoleUtils.cs deleted file mode 100644 index 5c58ef3b4..000000000 --- a/csharp/Lib/SysTools/Utils/ConsoleUtils.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Utils; - -internal static class ConsoleUtils -{ - public static T WriteLine(this T t, ConsoleColor color) - { - var c = Console.ForegroundColor; - - Console.ForegroundColor = color; - Console.WriteLine(t); - Console.ForegroundColor = c; - - return t; - } - - public static T WriteLine(this T t) - { - Console.WriteLine(t); - return t; - } - - public static T WriteLine(this T t, ConsoleColor color, params String[] more) - { - var c = Console.ForegroundColor; - - Console.ForegroundColor = color; - Console.WriteLine(t + " " + more.JoinWith(" ")); - Console.ForegroundColor = c; - - return t; - } - - public static T WriteLine(this T t, params String[] more) - { - Console.WriteLine(t + " " + more.JoinWith(" ")); - return t; - } - - - public static T Write(this T t, ConsoleColor color) - { - var c = Console.ForegroundColor; - - Console.ForegroundColor = color; - Console.Write(t); - Console.ForegroundColor = c; - - return t; - } - - public static T Write(this T t) - { - Console.Write(t); - return t; - } - - public static T Write(this T t, ConsoleColor color, params String[] more) - { - var c = Console.ForegroundColor; - - Console.ForegroundColor = color; - Console.Write(t + " " + more.JoinWith(" ")); - Console.ForegroundColor = c; - - return t; - } - - public static T Write(this T t, params String[] more) - { - Console.Write(t + " " + more.JoinWith(" ")); - return t; - } - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Utils/EnumerableUtils.cs b/csharp/Lib/SysTools/Utils/EnumerableUtils.cs deleted file mode 100644 index 9f02caacf..000000000 --- a/csharp/Lib/SysTools/Utils/EnumerableUtils.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Utils; - -internal static class EnumerableUtils -{ - - public static IEnumerable Pad(this IEnumerable src, Int32 length, T padding) - { - using var enumerator = src.GetEnumerator(); - while (enumerator.MoveNext() && length-- > 0) - yield return enumerator.Current; - - while (length-- > 0) - yield return padding; - } - - public static Dictionary> IndexColumn(this IEnumerable> src, UInt16 index) - { - var d = new Dictionary>(); - - foreach (var outer in src) - { - var inner = outer.ToList(); - var key = inner[index]; - d.Add(key, inner); - } - - return d; - } - - - public static IEnumerable<(TLeft left, TRight right)> Zip(IEnumerable left, - IEnumerable right) - { - using var l = left.GetEnumerator(); - using var r = right.GetEnumerator(); - while (l.MoveNext() && r.MoveNext()) - yield return (l.Current, r.Current); - } - - public static IEnumerator Enumerator(this T t) - { - yield return t; - } - - public static IEnumerable Enumerable(this T t) - { - yield return t; - } - - public static IEnumerable Flatten(this IEnumerable> src) => src.SelectMany(s => s); - - public static void ForEach(this IEnumerable enumerable, Action action) - { - foreach (var e in enumerable) - action(e); - } - - - - public static IEnumerable WhereNot(this IEnumerable enumerable, Func predicate) - { - return enumerable.Where(e => !predicate(e)); - } - - - public static IEnumerable EmptyIfNull(this IEnumerable enumerable) - { - return enumerable ?? new T[0]; - } - - // // https://stackoverflow.com/a/34006336/141397 - // public static Int32 CombineHashes(this ITuple tupled, Int32 seed = 1009, Int32 factor = 9176) - // { - // var hash = seed; - // - // for (var i = 0; i < tupled.Length; i++) - // { - // unchecked - // { - // hash = hash * factor + tupled[i].GetHashCode(); - // } - // } - // - // return hash; - // } - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Utils/StringUtils.cs b/csharp/Lib/SysTools/Utils/StringUtils.cs deleted file mode 100644 index c6a6ef12c..000000000 --- a/csharp/Lib/SysTools/Utils/StringUtils.cs +++ /dev/null @@ -1,208 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Utils; - -internal static class StringUtils -{ - - public static String NewLineBefore(this String s) - { - return Environment.NewLine + s; - } - - public static String AddTitle(this String s, String title) - { - return title.Underline() + s; - } - - public static String Underline(this String s) - { - return s.NewLine() + "".PadRight(s.Length, '-').NewLine(2); - } - - public static Boolean IsNullOrEmpty(this String s) - { - return String.IsNullOrEmpty(s); - } - - public static Boolean IsNullOrWhiteSpace(this String s) - { - return String.IsNullOrWhiteSpace(s); - } - - public static String Format(this Object value, String fmt) - { - return String.Format(fmt, value); - } - - public static String NewLine(this String s) - { - return s + Environment.NewLine; - } - - public static String NewLine(this String s, Int32 number) - { - return Enumerable - .Repeat(Environment.NewLine, number) - .Aggregate(s, (a, b) => a + b); - } - - public static String AppendSpaces(this String s, Int32 count = 4) - { - return s.PadRight(s.Length + count); - } - - public static String Append(this String s, String other) - { - return s + other; - } - - public static String Concat(this IEnumerable ss) - { - return String.Join("", ss); - } - - public static String SurroundWith(this String s, String before, String after = null) - { - return before + s + (after ?? before); - } - - - public static String ShellSingleQuote(this String s) - { - return $"'{s.Replace("'", @"'\''")}'"; - } - - public static String Quote(this String s) - { - return s.Replace(@"\", @"\\").Replace("\"", "\\\"").SurroundWith("\""); - } - - public static String Join(this IEnumerable strings) - { - return String.Join("", strings); - } - - public static String JoinWith(this IEnumerable strings, String separator) - { - return String.Join(separator, strings); - } - - public static String JoinToLines(this IEnumerable lines) - { - return String.Join(Environment.NewLine, lines); - } - - public static String JoinToLines(params String[] lines) - { - return String.Join(Environment.NewLine, lines); - } - - public static String Indent(this String text) - { - return " ".SideBySideWith(text, ""); - } - - public static String Indent(this String text, String indentor) - { - return indentor.SideBySideWith(text); - } - - public static IReadOnlyList SplitLines(this String s) - { - return s.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - } - - public static String SideBySideWith(this String left, String right, String separator = " ") - { - var leftLines = left .Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList(); - var rightLines = right.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList(); - - var leftWidth = leftLines.Count != 0 - ? leftLines.Max(l => l.Length) - : 0; - - var nLines = Math.Max(leftLines.Count, rightLines.Count); - - var leftPadded = leftLines .Pad(nLines, "").Select(l => l.PadRight(leftWidth)); - var rightPadded = rightLines.Pad(nLines, ""); - - return Enumerable - .Zip(leftPadded, rightPadded, (l, r) => String.Join(separator, l, r)) - .JoinToLines(); - } - - - public static String JoinWith(this IEnumerable args, String separator) - { - return String.Join(separator, args); - } - - public static IEnumerable GetLinesStartingWith(this IEnumerable lines, String prefix) - { - return lines.Where(l => l.StartsWith(prefix)); - } - - public static String GetLineStartingWith(this IEnumerable lines, String prefix) - { - return lines - .GetLinesStartingWith(prefix) - .Single(); - } - - - - public static ILookup ParseKeyValuesTextFile(this SysPath file) - { - var lines = file - .ReadLines() - .Select(l => l.Trim()) - .Where(l => l.Length > 0 && !l.StartsWith("#") && !l.StartsWith(";")); - - var keyValuePairs = from line in lines - let fields = line.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries) - let key = fields[0] - from value in fields.Skip(1) - select (key, value); - - return keyValuePairs.ToLookup(e => e.key, e => e.value); - } - - public static Boolean EndsWith(this String str, Char c) - { - if (str.Length <= 0) - return false; - - return str[str.Length - 1] == c; - } - - public static String CommonPrefix(String str, params String[] more) - { - var prefixLength = str - .TakeWhile((c, i) => more.All(s => i < s.Length && s[i] == c)) - .Count(); - - return str.Substring(0, prefixLength); - } - - - public static String AfterFirst(this String str, Char separator) - { - return str.Substring(str.IndexOf(separator) + 1); - } - - public static String AfterLast(this String str, Char separator) - { - return str.Substring(str.LastIndexOf(separator) + 1); - } - - public static String UntilLast(this String str, Char separator) - { - return str.Substring(0, str.LastIndexOf(separator)); - } - - public static String UntilFirst(this String str, Char separator) - { - return str.Substring(0, str.IndexOf(separator)); - } - - -} \ No newline at end of file diff --git a/csharp/Lib/SysTools/Utils/Utils.cs b/csharp/Lib/SysTools/Utils/Utils.cs deleted file mode 100644 index 026c63e8c..000000000 --- a/csharp/Lib/SysTools/Utils/Utils.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace InnovEnergy.Lib.SysTools.Utils; - -public static class Utils -{ - - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - public static DateTime FromUnixTime(UInt64 unixTime) - { - return Epoch.AddSeconds(unixTime); - } - - public static R ValueOrDefault(this Dictionary dict, T key) - { - return ValueOrDefault(dict, key, default); - } - - public static R ValueOrDefault(this Dictionary dict, T key, R defaultValue) - { - return dict.TryGetValue(key, out var value) ? value : defaultValue; - } - - public static void CopyFilesRecursively(String source, String target) - { - CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target)); - } - - public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) - { - foreach (var file in source.GetFiles()) - file.CopyTo(Path.Combine(target.FullName, file.Name)); - - foreach (var dir in source.GetDirectories()) - CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name)); - } - -} \ No newline at end of file From 28a0ef55300d3e1795f315d03c652dd1b1f414ce Mon Sep 17 00:00:00 2001 From: ig Date: Mon, 20 Mar 2023 15:17:52 +0100 Subject: [PATCH 07/13] put back cors, apparently it's still necessary, sigh --- csharp/App/Backend/Program.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 385c6250b..39c324dc8 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -10,10 +10,6 @@ public static class Program var builder = WebApplication.CreateBuilder(args); - //builder.Services.AddHttpContextAccessor(); - //builder.Services.AddEndpointsApiExplorer(); - //builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod())); - builder.Services.AddControllers(); builder.Services.AddSwaggerGen(c => { @@ -30,9 +26,8 @@ public static class Program app.UseSwaggerUI(); } - //app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ; + app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ; app.UseHttpsRedirection(); - //app.UseAuthorization(); app.MapControllers(); app.Run(); From e25de16f65646af2a18479c5d645d0a74cf615b3 Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:40:23 +0100 Subject: [PATCH 08/13] Run CleanUp on TaskPool --- csharp/App/Backend/Database/Db.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 80cd15454..ebdab555c 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -1,3 +1,4 @@ +using System.Reactive.Concurrency; using System.Reactive.Linq; using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes.Methods; @@ -15,10 +16,10 @@ public static partial class Db private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); - public static TableQuery Sessions => Connection.Table(); - public static TableQuery Folders => Connection.Table(); - public static TableQuery Installations => Connection.Table(); - public static TableQuery Users => Connection.Table(); + public static TableQuery Sessions => Connection.Table(); + public static TableQuery Folders => Connection.Table(); + public static TableQuery Installations => Connection.Table(); + public static TableQuery Users => Connection.Table(); public static TableQuery FolderAccess => Connection.Table(); public static TableQuery InstallationAccess => Connection.Table(); @@ -38,9 +39,10 @@ public static partial class Db }); Observable.Interval(TimeSpan.FromDays(0.5)) - .StartWith(0) // Do it right away (on startup) + .StartWith(0) // Do it right away (on startup) + .ObserveOn(TaskPoolScheduler.Default) .SelectMany(Cleanup) - .Subscribe(); // and then daily + .Subscribe(); } From 1b5baf90ae960597cc5adca746bc2d13227f9085 Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:42:49 +0100 Subject: [PATCH 09/13] Fix bug where properties were missing in json of installations and folders (were serialized as TreeNodes) --- csharp/App/Backend/Controllers/Controller.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs index d9228f964..3cd7cf127 100644 --- a/csharp/App/Backend/Controllers/Controller.cs +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -162,14 +162,19 @@ public class Controller : ControllerBase [HttpGet(nameof(GetAllFoldersAndInstallations))] - public ActionResult> GetAllFoldersAndInstallations(Token authToken) + public ActionResult> GetAllFoldersAndInstallations(Token authToken) { var user = Db.GetSession(authToken)?.User; if (user is null) return Unauthorized(); + + var foldersAndInstallations = user + .AccessibleFoldersAndInstallations() + .OfType(); // Important! JSON serializer must see Objects otherwise + // it will just serialize the members of TreeNode %&@#!!! - return new (user.AccessibleFoldersAndInstallations()); + return new (foldersAndInstallations); } From 8d7f0cd8bf7d753711b74a6b40fdb1fc76c93426 Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:45:50 +0100 Subject: [PATCH 10/13] Use users name instead of email for login (https://softwareengineering.stackexchange.com/a/30087) --- csharp/App/Backend/Controllers/Controller.cs | 2 +- csharp/App/Backend/DataTypes/TreeNode.cs | 6 +-- csharp/App/Backend/DataTypes/User.cs | 6 ++- csharp/App/Backend/Database/Create.cs | 6 --- csharp/App/Backend/Database/Read.cs | 41 ++--------------- csharp/App/Backend/Database/Update.cs | 45 +++---------------- csharp/App/Backend/db.sqlite | Bin 495616 -> 495616 bytes 7 files changed, 18 insertions(+), 88 deletions(-) diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs index 3cd7cf127..1995fbd99 100644 --- a/csharp/App/Backend/Controllers/Controller.cs +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -16,7 +16,7 @@ public class Controller : ControllerBase [HttpPost(nameof(Login))] public ActionResult Login(String username, String password) { - var user = Db.GetUserByEmail(username); + var user = Db.GetUserByName(username); if (user is null || !user.VerifyPassword(password)) return Unauthorized(); diff --git a/csharp/App/Backend/DataTypes/TreeNode.cs b/csharp/App/Backend/DataTypes/TreeNode.cs index de24da0b8..796ab9cc2 100644 --- a/csharp/App/Backend/DataTypes/TreeNode.cs +++ b/csharp/App/Backend/DataTypes/TreeNode.cs @@ -5,9 +5,9 @@ namespace InnovEnergy.App.Backend.DataTypes; public abstract partial class TreeNode { [PrimaryKey, AutoIncrement] - public Int64 Id { get; set; } - public String Name { get; set; } = ""; - public String Information { get; set; } = ""; // unstructured random info + public Int64 Id { get; set; } + public virtual String Name { get; set; } = ""; // overridden by User (unique) + public String Information { get; set; } = ""; // unstructured random info [Indexed] // parent/child relation public Int64 ParentId { get; set; } diff --git a/csharp/App/Backend/DataTypes/User.cs b/csharp/App/Backend/DataTypes/User.cs index 70ea9e034..449285fe3 100644 --- a/csharp/App/Backend/DataTypes/User.cs +++ b/csharp/App/Backend/DataTypes/User.cs @@ -4,11 +4,13 @@ namespace InnovEnergy.App.Backend.DataTypes; public class User : TreeNode { - [Indexed] public String Email { get; set; } = null!; public Boolean HasWriteAccess { get; set; } = false; public String Language { get; set; } = null!; public String Password { get; set; } = null!; - + + [Unique] + public override String Name { get; set; } = null!; + // TODO: must reset pwd } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs index d1b6d8318..40b40cf2c 100644 --- a/csharp/App/Backend/Database/Create.cs +++ b/csharp/App/Backend/Database/Create.cs @@ -1,5 +1,4 @@ using InnovEnergy.App.Backend.DataTypes; -using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.Relations; @@ -21,11 +20,6 @@ public static partial class Db public static Boolean Create(User user) { - if (GetUserByEmail(user.Email) is not null) - return false; - - user.Password = user.SaltAndHashPassword(user.Password); - return Connection.Insert(user) > 0; } diff --git a/csharp/App/Backend/Database/Read.cs b/csharp/App/Backend/Database/Read.cs index 53c0586de..5a4eae7bb 100644 --- a/csharp/App/Backend/Database/Read.cs +++ b/csharp/App/Backend/Database/Read.cs @@ -7,13 +7,13 @@ namespace InnovEnergy.App.Backend.Database; public static partial class Db { - public static Folder? GetFolderById(Int64 id) + public static Folder? GetFolderById(Int64? id) { return Folders .FirstOrDefault(f => f.Id == id); } - public static Installation? GetInstallationById(Int64 id) + public static Installation? GetInstallationById(Int64? id) { return Installations .FirstOrDefault(i => i.Id == id); @@ -25,22 +25,10 @@ public static partial class Db .FirstOrDefault(u => u.Id == id); } - // private!! - private static Session? GetSessionById(Int64 id) - { - #pragma warning disable CS0618 - - return Sessions - .FirstOrDefault(u => u.Id == id); - - #pragma warning restore CS0618 - } - - - public static User? GetUserByEmail(String email) + public static User? GetUserByName(String userName) { return Users - .FirstOrDefault(u => u.Email == email); + .FirstOrDefault(u => u.Name == userName); } public static Session? GetSession(String token) @@ -62,25 +50,4 @@ public static partial class Db return session; } - - public static User? GetUserBySessionToken(String token) - { - var session = Sessions - .FirstOrDefault(s => s.Token == token); - - // cannot user session.Expired in the DB query above. - // It does not exist in the db (IgnoreAttribute) - - if (session is null) - return null; - - if (!session.Valid) - { - Delete(session); - return null; - } - - return GetUserById(session.UserId); - } - } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs index 72aef3015..923d81345 100644 --- a/csharp/App/Backend/Database/Update.cs +++ b/csharp/App/Backend/Database/Update.cs @@ -1,7 +1,4 @@ using InnovEnergy.App.Backend.DataTypes; -using InnovEnergy.App.Backend.DataTypes.Methods; -using InnovEnergy.App.Backend.Relations; - namespace InnovEnergy.App.Backend.Database; @@ -10,54 +7,24 @@ public static partial class Db { public static Boolean Update(Folder folder) { - if (folder.IsRelativeRoot()) // TODO: triple check - { - var original = GetFolderById(folder.Id); - if (original is null) - return false; - - folder.ParentId = original.ParentId; - } - - return Connection.InsertOrReplace(folder) > 0; + return Connection.Update(folder) > 0; } public static Boolean Update(Installation installation) { - if (installation.IsRelativeRoot()) // TODO: triple check - { - var original = GetInstallationById(installation.Id); - if (original is null) - return false; - - installation.ParentId = original.ParentId; - } - - return Connection.InsertOrReplace(installation) > 0; + return Connection.Update(installation) > 0; } - public static Boolean Update(User user) { var originalUser = GetUserById(user.Id); return originalUser is not null - && user.Id == originalUser.Id // these columns must not be modified! - && user.ParentId == originalUser.ParentId - && user.Email == originalUser.Email - && Connection.InsertOrReplace(user) > 0; + && user.ParentId == originalUser.ParentId // these columns must not be modified! + && user.Name == originalUser.Name + && Connection.Update(user) > 0; } - public static Boolean Update(this Session session) - { - #pragma warning disable CS0618 - var originalSession = GetSessionById(session.Id); - #pragma warning restore CS0618 - - return originalSession is not null - && session.Token == originalSession.Token // these columns must not be modified! - && session.UserId == originalSession.UserId - && Connection.InsertOrReplace(session) > 0; - } + } \ No newline at end of file diff --git a/csharp/App/Backend/db.sqlite b/csharp/App/Backend/db.sqlite index 8cddf61095957479fb5c2d7cbfd90f5ec4235b82..5ed0b4ad005558b386a10801523b4edb0a182e30 100644 GIT binary patch delta 66690 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­L`Ccw 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 IZEXtw3*QU(WdHyG From 9098ac81b5280a7f80e0658e9a9b72db854e9f1e Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:46:38 +0100 Subject: [PATCH 11/13] prevent rider from automatically opening swagger on each launch --- .../App/Backend/Properties/launchSettings.json | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/csharp/App/Backend/Properties/launchSettings.json b/csharp/App/Backend/Properties/launchSettings.json index 062a71f67..1729b03cd 100644 --- a/csharp/App/Backend/Properties/launchSettings.json +++ b/csharp/App/Backend/Properties/launchSettings.json @@ -1,31 +1,15 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:63205", - "sslPort": 44319 - } - }, "profiles": { "Backend": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, + "launchUrl": "swagger", "applicationUrl": "https://localhost:7087;http://localhost:5031", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } From 381df5f38d0b647ad9c41c72fce1d30f7f390f2b Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:49:17 +0100 Subject: [PATCH 12/13] fix handling of relative roots --- .../App/Backend/DataTypes/Methods/Folder.cs | 27 ++----- .../Backend/DataTypes/Methods/Installation.cs | 21 +++--- .../App/Backend/DataTypes/Methods/Session.cs | 71 ++++++++++--------- .../App/Backend/DataTypes/Methods/TreeNode.cs | 24 +++++++ csharp/App/Backend/DataTypes/Methods/User.cs | 38 +++++----- csharp/App/Backend/Program.cs | 3 + csharp/App/Backend/Relations/Session.cs | 5 +- 7 files changed, 100 insertions(+), 89 deletions(-) create mode 100644 csharp/App/Backend/DataTypes/Methods/TreeNode.cs diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs index 68c12c68b..ee93eeab8 100644 --- a/csharp/App/Backend/DataTypes/Methods/Folder.cs +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -68,36 +68,17 @@ public static class FolderMethods public static Folder? Parent(this Folder folder) { - return IsAbsoluteRoot(folder) + return IsRoot(folder) ? null : Db.GetFolderById(folder.ParentId); } - public static Boolean IsAbsoluteRoot(this Folder folder) + public static Boolean IsRoot(this Folder folder) { - return folder.ParentId == 0; // root has ParentId 0 by definition + return folder.ParentId <= 0 + && Db.GetFolderById(folder.Id)?.Id == 0; // might have been 0 because it is a relative root } - public static Boolean IsRelativeRoot(this Folder folder) - { - return folder.ParentId < 0; // TODO - } - - public static Boolean WasMoved(this Folder folder) - { - if (folder.IsRelativeRoot()) - return false; - - var existingFolder = Db.GetFolderById(folder.Id); - - return existingFolder is not null - && existingFolder.ParentId != folder.ParentId; - } - - public static Boolean Exists(this Folder folder) - { - return Db.Folders.Any(f => f.Id == folder.Id); - } } diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index 134db9c11..d9fc72728 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -76,21 +76,20 @@ public static class InstallationMethods public static Folder? Parent(this Installation installation) { - return installation.IsRelativeRoot() - ? null - : Db.GetFolderById(installation.ParentId); + if (installation.ParentId <= 0) // relative root + { + var i = Db.GetInstallationById(installation.Id); + if (i is null) + return null; + + installation = i; + } + + return Db.GetFolderById(installation.ParentId); } - public static Boolean IsRelativeRoot(this Installation i) - { - return i.ParentId < 0; - } - public static Boolean WasMoved(this Installation installation) { - if (installation.IsRelativeRoot()) - return false; - var existingInstallation = Db.GetInstallationById(installation.Id); return existingInstallation is not null diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index a1ecf900c..5d5d2b4e7 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -1,5 +1,6 @@ using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Relations; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.App.Backend.DataTypes.Methods; @@ -13,20 +14,23 @@ public static class SessionMethods && folder is not null && user.HasWriteAccess && user.HasAccessTo(folder.Parent()) - && Db.Create(folder) + && Db.Create(folder) // TODO: these two in a transaction && Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id }); } public static Boolean Update(this Session? session, Folder? folder) { - var user = session?.User; + var user = session?.User; + var original = Db.GetFolderById(folder?.Id); return user is not null && folder is not null + && original is not null && user.HasWriteAccess && user.HasAccessTo(folder) - && (folder.IsRelativeRoot() || user.HasAccessTo(folder.Parent())) - && Db.Update(folder); + && folder + .WithParentOf(original) // prevent moving + .Apply(Db.Update); } public static Boolean Delete(this Session? session, Folder? folder) @@ -49,24 +53,27 @@ public static class SessionMethods && installation is not null && user.HasWriteAccess && user.HasAccessTo(installation.Parent()) - && Db.Create(installation) + && Db.Create(installation) // TODO: these two in a transaction && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }) && await installation.CreateBucket() && await installation.RenewS3BucketUrl(); // generation of access _after_ generation of - // bucket to prevent "zombie" access-rights. + // bucket to prevent "zombie" access-rights. } public static Boolean Update(this Session? session, Installation? installation) { var user = session?.User; + + var original = Db.GetInstallationById(installation?.Id); return user is not null && installation is not null + && original is not null && user.HasWriteAccess - && installation.Exists() && user.HasAccessTo(installation) - && (installation.IsRelativeRoot() || user.HasAccessTo(installation.Parent())) // TODO: triple check this - && Db.Update(installation); + && installation + .WithParentOf(original) // prevent moving + .Apply(Db.Update); } public static Boolean Delete(this Session? session, Installation? installation) @@ -77,37 +84,37 @@ public static class SessionMethods && installation is not null && user.HasWriteAccess && user.HasAccessTo(installation) - // && installation.DeleteBucket().Result // TODO: await? + // && installation.DeleteBucket().Result // TODO && Db.Delete(installation); } public static Boolean Create(this Session? session, User? newUser) { var sessionUser = session?.User; - - if (sessionUser is null || newUser is null || !sessionUser.HasWriteAccess) - return false; - newUser.ParentId = sessionUser.Id; // Important! - - return Db.Create(newUser); + return sessionUser is not null + && newUser is not null + && sessionUser.HasWriteAccess + && newUser + .WithParent(sessionUser) + .Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password)) + .Apply(Db.Create); } public static Boolean Update(this Session? session, User? editedUser) { var sessionUser = session?.User; - if (editedUser == null || sessionUser == null) return false; + var originalUser = Db.GetUserById(editedUser?.Id); - // TODO: make specific method for changing user account settings like pwd - // Password change is only allowed for oneself - editedUser.Password = editedUser.Id != sessionUser.Id - ? sessionUser.Password - : sessionUser.SaltAndHashPassword(editedUser.Password); - - return sessionUser.HasWriteAccess - && sessionUser.HasAccessTo(editedUser) - && (editedUser.IsRelativeRoot() || sessionUser.HasAccessTo(editedUser.Parent()) || editedUser.Id == sessionUser.Id) // TODO: triple check this - && Db.Update(editedUser); + return editedUser is not null + && sessionUser is not null + && originalUser is not null + && sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(editedUser) + && editedUser + .WithParentOf(originalUser) // prevent moving + .WithPasswordOf(originalUser) + .Apply(Db.Update); } public static Boolean Delete(this Session? session, User? userToDelete) @@ -117,7 +124,7 @@ public static class SessionMethods return sessionUser is not null && userToDelete is not null && sessionUser.HasWriteAccess - && sessionUser.HasAccessTo(userToDelete) // TODO: && user.HasAccessTo(installation.Parent()) ??? + && sessionUser.HasAccessTo(userToDelete) && Db.Delete(userToDelete); } @@ -127,8 +134,8 @@ public static class SessionMethods var sessionUser = session?.User; return sessionUser is not null - && user is not null && installation is not null + && user is not null && user.IsDescendantOf(sessionUser) && sessionUser.HasAccessTo(installation) && !user.HasAccessTo(installation) @@ -140,8 +147,8 @@ public static class SessionMethods var sessionUser = session?.User; return sessionUser is not null - && user is not null && folder is not null + && user is not null && user.IsDescendantOf(sessionUser) && sessionUser.HasAccessTo(folder) && !user.HasAccessTo(folder) @@ -153,8 +160,8 @@ public static class SessionMethods var sessionUser = session?.User; return sessionUser is not null - && user is not null && installation is not null + && user is not null && user.IsDescendantOf(sessionUser) && sessionUser.HasAccessTo(installation) && user.HasAccessTo(installation) @@ -166,8 +173,8 @@ public static class SessionMethods var sessionUser = session?.User; return sessionUser is not null - && user is not null && folder is not null + && user is not null && user.IsDescendantOf(sessionUser) && sessionUser.HasAccessTo(folder) && user.HasAccessTo(folder) diff --git a/csharp/App/Backend/DataTypes/Methods/TreeNode.cs b/csharp/App/Backend/DataTypes/Methods/TreeNode.cs new file mode 100644 index 000000000..3c8a530bc --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/TreeNode.cs @@ -0,0 +1,24 @@ +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class TreeNodeMethods +{ + + public static T WithParentOf(this T treeNode, T other) where T: TreeNode + { + treeNode.ParentId = other.ParentId; + return treeNode; + } + + public static T WithParent(this T treeNode, T other) where T: TreeNode + { + treeNode.ParentId = other.Id; + return treeNode; + } + + public static T HideParent(this T treeNode) where T: TreeNode + { + treeNode.ParentId = 0; + return treeNode; + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs index 410b4680b..5907b7c1e 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -34,7 +34,7 @@ public static class UserMethods // to a child folder of a folder he has already access to // TODO shouldn't we prevent doubling permissions? -K" // TODO yes we should -ig (still TODO) - // however we should leave the distinct, defensive programming... + // however we should still leave the distinct, defensive programming... } public static IEnumerable AccessibleFoldersAndInstallations(this User user) @@ -51,9 +51,9 @@ public static class UserMethods .InstallationAccess .Where(r => r.UserId == user.Id) .Select(r => r.InstallationId) - .Select(Db.GetInstallationById) + .Select(i => Db.GetInstallationById(i)) .NotNull() - .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user + .Do(i => i.HideParent()); // hide inaccessible parents from calling user } public static IEnumerable DirectlyAccessibleFolders(this User user) @@ -62,9 +62,9 @@ public static class UserMethods .FolderAccess .Where(r => r.UserId == user.Id) .Select(r => r.FolderId) - .Select(Db.GetFolderById) + .Select(i => Db.GetFolderById(i)) .NotNull() - .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user; + .Do(f => f.HideParent()); // hide inaccessible parents from calling user; } public static IEnumerable ChildUsers(this User parent) @@ -112,21 +112,17 @@ public static class UserMethods public static User? Parent(this User u) { - return u.IsAbsoluteRoot() + return u.IsRoot() ? null : Db.GetUserById(u.ParentId); } - public static Boolean IsAbsoluteRoot(this User u) - { - return u.ParentId == 0; + public static Boolean IsRoot(this User user) + { + return user.ParentId <= 0 + && Db.GetUserById(user.Id)?.Id == 0; // might have been 0 because it is a relative root } - public static Boolean IsRelativeRoot(this User u) - { - return u.ParentId < 0; - } - public static Boolean HasDirectAccessTo(this User user, Folder folder) { return Db @@ -174,12 +170,6 @@ public static class UserMethods .Contains(user); } - public static Boolean IsRelativeRoot(this User user, Installation i) - { - // TODO: determine not by id but by accessibility - return i.ParentId < 0; - } - public static String Salt(this User user) { // + id => salt unique per user @@ -188,6 +178,14 @@ public static class UserMethods return $"{user.Id}InnovEnergy"; } + public static User WithPasswordOf(this User user, User other) + { + user.Password = other.Password; + return user; + } + + + // TODO? private static Boolean IsValidEmail(String email) diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 39c324dc8..4a24d1aa7 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -4,6 +4,9 @@ namespace InnovEnergy.App.Backend; public static class Program { + + // TODO: Trash + public static void Main(String[] args) { //Db.CreateFakeRelations(); diff --git a/csharp/App/Backend/Relations/Session.cs b/csharp/App/Backend/Relations/Session.cs index fa9a993d4..03af863ee 100644 --- a/csharp/App/Backend/Relations/Session.cs +++ b/csharp/App/Backend/Relations/Session.cs @@ -1,6 +1,5 @@ using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.DataTypes; -using InnovEnergy.Lib.Utils; using SQLite; namespace InnovEnergy.App.Backend.Relations; @@ -13,8 +12,8 @@ public class Session : Relation [Indexed] public Int64 UserId { get => Right; init => Right = value;} [Indexed] public DateTime LastSeen { get; set; } - [Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge - && !User.Email.IsNullOrEmpty(); + [Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge + && (User) is not null; [Ignore] public User User => _User ??= Db.GetUserById(UserId)!; From abcec0ae10588c50170c59126ad8f9ec15d67d8f Mon Sep 17 00:00:00 2001 From: ig Date: Tue, 21 Mar 2023 11:56:34 +0100 Subject: [PATCH 13/13] remove HeaderFilter.cs, no longer needed --- csharp/App/Backend/HeaderFilter.cs | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 csharp/App/Backend/HeaderFilter.cs diff --git a/csharp/App/Backend/HeaderFilter.cs b/csharp/App/Backend/HeaderFilter.cs deleted file mode 100644 index f3520b75e..000000000 --- a/csharp/App/Backend/HeaderFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace InnovEnergy.App.Backend; - -/// -/// This is for convenient testing! Todo throw me out? -/// Operation filter to add the requirement of the custom header -/// -public class HeaderFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - operation.Parameters ??= new List(); - - operation.Parameters.Add(new OpenApiParameter - { - Name = "auth", - In = ParameterLocation.Header, - Content = new Dictionary(), - Required = false - }); - } -} \ No newline at end of file