Compare commits

..

38 Commits

Author SHA1 Message Date
8d55c1c533 implemented check of HttpMethod as binary flags 2025-05-22 21:42:13 +05:00
e4ee03364c updated dependencies 2025-05-22 20:43:55 +05:00
c77b3e0742 LaunchArgumentParser.UnknownArguments 2025-04-26 05:29:30 +05:00
e391f0238a DTLib v1.7.2 2025-04-26 04:20:40 +05:00
823169ca91 LaunchArgumentParser.HelpMessageHeader 2025-04-26 04:19:14 +05:00
ee20c9c5ec added requestLogger to IRouteHandler 2025-04-06 14:59:27 +05:00
af658b1656 Composite logger fix for different child logger severity settings 2025-04-06 14:51:23 +05:00
26c118195c small changes in ColoredConsole 2025-04-06 13:50:42 +05:00
c461418185 refactored LaunchArgumentParser 2025-04-06 13:25:44 +05:00
29cde170c6 abstract class replaced with IRouteHandler interface 2025-04-06 12:19:11 +05:00
448161239e response elapsed time logging 2025-03-24 00:31:47 +05:00
a4c2ae3e28 HomeRoute removed (use MapRoute "/") 2025-03-23 02:44:51 +05:00
1eb208cba3 HttpMethod 2025-03-23 02:36:47 +05:00
2b5d6b6a54 fixes for ServeFilesRouteHandler 2025-03-23 01:20:25 +05:00
386d71260c added IOPath double separator removal 2025-03-23 01:19:32 +05:00
dc35725b64 Task.AsCancellable 2025-03-23 00:09:40 +05:00
7d814ee4cb IRouter 2025-03-23 00:07:06 +05:00
9082d7a4d0 Created new project: DTLib.Web 2025-03-22 23:36:29 +05:00
f9e4e533bb WriteAllText return type fix 2025-03-22 21:21:37 +05:00
4da75bfa8a v1.6.2 2025-03-22 20:55:10 +05:00
cfa26ce807 async methods in File 2025-03-22 20:53:16 +05:00
f2cdfc86b7 TransformStream 2024-11-03 22:01:02 +05:00
fa9c5ac689 log format 2024-11-03 21:44:03 +05:00
613b11e88c ColoredConsole rework 2024-10-20 12:32:30 +05:00
052169df1c pack.sh package selection 2024-10-20 10:36:57 +05:00
826c11aa55 FileMode.Create 2024-09-29 09:22:03 +05:00
7fccb3810f Filesystem 2024-09-26 03:09:28 +05:00
4c08ad0064 improvements for File and Timer 2024-09-22 23:09:33 +05:00
2c790f9b29 migration to my git server 2024-09-20 02:57:30 +05:00
359c8e25ca chmod +x 2024-09-20 02:05:40 +05:00
249627dc78 merged projects DTLib.Dtsod, DTLib.Network and DTLib.Logging into DTLib project 2024-09-20 01:45:33 +05:00
c31370f37a Timer rework 2024-09-20 00:27:57 +05:00
4b33339d3a ConsoleWrapperLogger 2024-01-23 13:55:16 +06:00
245f631ed5 xxhash update 2024-01-07 21:00:59 +06:00
47cc208cc8 version dump and Publish configuration removal 2024-01-07 20:58:37 +06:00
eef912d4c5 deleted Hasher from DTLib 2024-01-07 20:45:16 +06:00
5a55a69069 submodule XXHash 2024-01-07 20:39:00 +06:00
5297231205 fsp change 2024-01-07 14:44:44 +06:00
115 changed files with 994 additions and 35386 deletions

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@ nuget/
.idea/
.editorconfig
*.user
*.DotSettings
#backups
.old*/

9
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "DTLib.Ben.Demystifier"]
path = DTLib.Ben.Demystifier
url = https://github.com/Timerix22/DTLib.Ben.Demystifier.git
[submodule "DTLib.Demystifier"]
path = DTLib.Demystifier
url = https://timerix.ddns.net:3322/Timerix/DTLib.Demystifier.git
[submodule "DTLib.XXHash"]
path = DTLib.XXHash
url = https://timerix.ddns.net:3322/Timerix/DTLib.XXHash.git

1
DTLib.Demystifier Submodule

Submodule DTLib.Demystifier added at 4eaade6e92

View File

@@ -1,39 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Dtsod</PackageId>
<Version>1.3.3</Version>
<Authors>Timerix</Authors>
<Description>Definitely not json</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/DTLib</RepositoryUrl>
<PackageProjectUrl>https://github.com/Timerix22/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<!--language features-->
<LangVersion>11</LangVersion>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<!--external dependencies-->
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.3.3" />
</ItemGroup>
<!--project files-->
<ItemGroup>
<Compile Remove="Experimental/**/*" />
</ItemGroup>
</Project>

View File

@@ -1,328 +0,0 @@
namespace DTLib.Dtsod;
// v21
// парсер теперь не может игнорировать комменты, потом починю
// теперь числовые значения конвертируются в правильный тип, а не в int64/uint64 (новый вариант switch из c#9.0 делал какую-то дичь)
// исправлены некоторые другие баги
public class DtsodV21 : Dictionary<string, dynamic>, IDtsod
{
public DtsodVersion Version { get; } = DtsodVersion.V21;
public IDictionary<string, dynamic> ToDictionary() => this;
readonly string Text;
//public Dictionary<string, dynamic> Values { get; set; }
public DtsodV21(string text)
{
Text = text;
foreach (KeyValuePair<string, dynamic> pair in Parse(text))
Add(pair.Key, pair.Value);
}
public DtsodV21(IDictionary<string, dynamic> rawDict)
{
Text = "";
foreach (KeyValuePair<string, dynamic> pair in rawDict)
Add(pair.Key, pair.Value);
}
// выдаёт Exception
public new dynamic this[string key]
{
get => TryGetValue(key, out dynamic value) ? value : throw new Exception($"Dtsod[{key}] key not found");
set
{
if (!TrySetValue(key, value)) throw new Exception($"Dtsod[{key}] key not found");
}
}
// не выдаёт KeyNotFoundException
public new bool TryGetValue(string key, out dynamic value)
{
try
{
value = base[key];
return true;
}
catch (KeyNotFoundException)
{
value = null;
return false;
}
}
public bool TrySetValue(string key, dynamic value)
{
try
{
base[key] = value;
return true;
}
catch (KeyNotFoundException)
{
return false;
}
}
public override string ToString() => Text;
enum ValueType
{
List,
Complex,
String,
Default
}
Dictionary<string, dynamic> Parse(string text)
{
Dictionary<string, dynamic> parsed = new();
int i = 0;
for (; i < text.Length; i++)
ReadName();
return parsed;
// СЛОМАНО
/*void ReadCommentLine()
{
for (; i < text.Length && text[i] != '\n'; i++) DebugNoTime("h", text[i].ToString());
}*/
void ReadName()
{
bool isListElem = false;
dynamic value;
StringBuilder defaultNameBuilder = new();
for (; i < text.Length; i++)
{
switch (text[i])
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case ':':
i++;
string name = defaultNameBuilder.ToString();
value = ReadValue();
// если value это null, эта строка выдавала ошибку
//DebugNoTime("c", $"parsed.Add({name}, {value} { value.GetType() })");
if (isListElem)
{
if (!parsed.ContainsKey(name))
parsed.Add(name, new List<dynamic>());
parsed[name].Add(value);
}
else
parsed.Add(name, value);
return;
// строка, начинающаяся с # будет считаться комментом
case '#':
//ReadCommentLine();
break;
case '}':
throw new Exception("Parse.ReadName() error: unexpected '}' at " + i + " char");
// если $ перед названием параметра поставить, значение value добавится в лист с названием name
case '$':
if (defaultNameBuilder.ToString().Length != 0)
throw new Exception("Parse.ReadName() error: unexpected '$' at " + i + " char");
isListElem = true;
break;
case ';':
throw new Exception("Parse.ReadName() error: unexpected ';' at " + i + " char");
default:
defaultNameBuilder.Append(text[i]);
break;
}
}
}
dynamic ReadValue()
{
ValueType type = ValueType.Default;
dynamic value = null;
string ReadString()
{
i++;
StringBuilder valueBuilder = new();
valueBuilder.Append('"');
for (; text[i] != '"' || text[i - 1] == '\\'; i++)
{
valueBuilder.Append(text[i]);
}
valueBuilder.Append('"');
type = ValueType.String;
return valueBuilder.ToString();
}
List<dynamic> ReadList()
{
i++;
List<dynamic> output = new();
StringBuilder valueBuilder = new();
for (; text[i] != ']'; i++)
{
switch (text[i])
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case ',':
ParseValueToRightType(valueBuilder.ToString());
output.Add(value);
valueBuilder.Clear();
break;
default:
valueBuilder.Append(text[i]);
break;
}
}
if (valueBuilder.Length > 0)
{
ParseValueToRightType(valueBuilder.ToString());
output.Add(value);
}
type = ValueType.List;
return output;
}
Dictionary<string, dynamic> ReadComplex()
{
StringBuilder valueBuilder = new();
int balance = 1;
i++;
for (; balance != 0; i++)
{
switch (text[i])
{
case '"':
valueBuilder.Append(ReadString());
break;
case '}':
balance--;
if (balance != 0)
valueBuilder.Append(text[i]);
break;
case '{':
balance++;
valueBuilder.Append(text[i]);
break;
default:
valueBuilder.Append(text[i]);
break;
}
}
i--; // i++ в for выполняется даже когда balance == 0, то есть text[i] получается == ;, что ломает всё
type = ValueType.Complex;
return Parse(valueBuilder.ToString());
}
void ParseValueToRightType(string stringValue)
{
switch (stringValue)
{
// bool
case "true":
case "false":
value = stringValue.ToBool();
break;
// null
case "null":
value = null;
break;
default:
if (stringValue.Contains('"'))
value = stringValue.Remove(stringValue.Length - 1).Remove(0, 1);
// double
else if (stringValue.Contains('.'))
value = stringValue.ToDouble();
// ushort; ulong; uint
else if (stringValue.Length > 2 && stringValue[stringValue.Length - 2] == 'u')
{
switch (stringValue[stringValue.Length - 1])
{
case 's':
value = stringValue.Remove(stringValue.Length - 2).ToUShort();
break;
case 'i':
value = stringValue.Remove(stringValue.Length - 2).ToUInt();
break;
case 'l':
value = stringValue.Remove(stringValue.Length - 2).ToULong();
break;
default:
throw new Exception($"Dtsod.Parse.ReadValue() error: value= wrong type <u{stringValue[stringValue.Length - 1]}>");
}
}
// short; long; int
else
switch (stringValue[stringValue.Length - 1])
{
case 's':
value = stringValue.Remove(stringValue.Length - 1).ToShort();
break;
case 'l':
value = stringValue.Remove(stringValue.Length - 1).ToLong();
break;
default:
value = stringValue.ToInt();
break;
}
break;
}
}
StringBuilder defaultValueBuilder = new();
for (; i < text.Length; i++)
{
switch (text[i])
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case '"':
value = ReadString();
break;
case ';':
switch (type)
{
case ValueType.String:
ParseValueToRightType(value);
break;
case ValueType.Default:
ParseValueToRightType(defaultValueBuilder.ToString());
break;
}
return value;
case '[':
value = ReadList();
break;
case '{':
value = ReadComplex();
break;
// строка, начинающаяся с # будет считаться комментом
case '#':
//ReadCommentLine();
break;
default:
defaultValueBuilder.Append(text[i]);
break;
}
}
throw new Exception("Dtsod.Parse.ReadValue error: wtf it's the end of function");
}
}
}

View File

@@ -1,13 +0,0 @@
namespace DTLib.Dtsod;
public enum DtsodVersion : byte
{
V21 = 21,
V22 = 22,
V23 = 23,
V24 = 24,
V25,
#if DEBUG
V30 = 30
#endif
}

View File

@@ -1,39 +0,0 @@
namespace DTLib.Dtsod.ClassSerializer;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)]
public class DtsodSerializableAttribute : Attribute
{
public DtsodVersion Version;
public DtsodSerializableAttribute(DtsodVersion ver) => Version = ver;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SerializeAsAttribute: Attribute
{
public string Key;
/// <summary>
/// use it only for base types
/// </summary>
/// <param name="key">name the field will be serialized as</param>
public SerializeAsAttribute(string key) => Key = key;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class SerializationMethodAttribute<TSource,TSerialized> : Attribute
{
public Func<TSource, TSerialized> Method;
/// <summary>
/// </summary>
/// <param name="method">how to serialize field</param>
public SerializationMethodAttribute(Func<TSource, TSerialized> method) => Method = method;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class DeserializationMethodAttribute<TSource,TSerialized> : Attribute
{
public Func<TSerialized, TSource> Method;
/// <summary>
/// </summary>
/// <param name="method">how to deserialize field</param>
public DeserializationMethodAttribute( Func<TSerialized, TSource> method) => Method = method;
}

View File

@@ -1,298 +0,0 @@
using System.Globalization;
using DTLib.Dtsod.ClassSerializer;
namespace DTLib.Dtsod;
public class DtsodV30 : DtsodDict<string, dynamic>, IDtsod
{
public DtsodVersion Version { get; } = DtsodVersion.V30;
public IDictionary<string, dynamic> ToDictionary() => this;
public DtsodV30() : base() => UpdateLazy();
public DtsodV30(IDictionary<string, dynamic> dict) : base(dict) => UpdateLazy();
public DtsodV30(string serialized) : this() => Append(Deserialize(serialized));
static IDictionary<string, dynamic> Deserialize(string text)
{
char c;
int i = -1; // ++i в ReadType
StringBuilder b = new();
Type ReadType()
{
while (i < text.Length - 1)
{
c = text[++i];
switch (c)
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case '#':
SkipComment();
break;
case ':':
string _type = b.ToString();
b.Clear();
return TypeHelper.Instance.TypeFromString(_type);
case '=':
case '"':
case '\'':
case ';':
case '[':
case ']':
case '{':
case '}':
throw new Exception($"DtsodV30.Deserialize() error: unexpected {c}");
default:
b.Append(c);
break;
}
}
throw new Exception("DtsodV30.Deserialize.ReadType() error: end of text\ntext:\n" + text);
}
string ReadName()
{
while (i < text.Length - 1)
{
c = text[++i];
switch (c)
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case '#':
SkipComment();
break;
case '=':
string _name = b.ToString();
b.Clear();
return _name;
case ':':
case '"':
case '\'':
case ';':
case '[':
case ']':
case '{':
case '}':
throw new Exception($"DtsodV30.Deserialize() error: unexpected {c}");
default:
b.Append(c);
break;
}
}
throw new Exception("DtsodV30.Deserialize.ReadName() error: end of text\ntext:\n" + text);
}
object ReadValue(ref bool endoflist)
{
void ReadString()
{
c = text[++i]; //пропускает начальный символ '"'
while ((c != '"' && c != '\'') || (text[i - 1] == '\\' && text[i - 2] != '\\'))
{
b.Append(c);
if (++i >= text.Length) throw new Exception("DtsodV30.Deserialize() error: end of text\ntext:\n" + text);
c = text[i];
}
}
List<object> ReadList()
{
List<object> list = new();
bool _endoflist = false;
while (!_endoflist)
list.Add(CreateInstance(ReadType(), ReadValue(ref _endoflist)));
b.Clear();
return list;
}
IDictionary<string, dynamic> ReadDictionary()
{
short bracketBalance = 1;
c = text[++i]; //пропускает начальный символ '{'
while (bracketBalance != 0)
{
switch (c)
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case '#':
SkipComment();
break;
case '{':
bracketBalance++;
b.Append(c);
break;
case '}':
bracketBalance--;
if (bracketBalance != 0)
b.Append(c);
break;
case '"':
case '\'':
b.Append('"');
ReadString();
b.Append('"');
break;
default:
b.Append(c);
break;
}
if (++i >= text.Length) throw new Exception("DtsodV30.Deserialize() error: end of text\ntext:\n" + text);
c = text[i];
}
b.Clear();
return Deserialize(b.ToString());
}
while (i < text.Length-1)
{
c = text[++i];
switch (c)
{
case ' ':
case '\t':
case '\r':
case '\n':
break;
case '#':
SkipComment();
break;
case '"':
case '\'':
ReadString();
break;
case ';': // один параметр
case ',': // для листов
string str = b.ToString();
b.Clear();
return str == "null" ? null : str;
case '[':
return ReadList();
case ']':
endoflist = true;
goto case ',';
case '{':
return ReadDictionary();
case '=':
case ':':
case '}':
throw new Exception($"DtsodV30.Deserialize() error: unexpected {c}");
default:
b.Append(c);
break;
}
}
throw new Exception("DtsodV30.Deserialize.ReadValue() error: end of text\ntext:\n" + text);
}
void SkipComment()
{
while (text[i] != '\n')
if (++i >= text.Length) throw new Exception("DtsodV30.Deserialize() error: end of text\ntext:\n" + text);
}
object CreateInstance(Type type, object ctor_arg)
{
if (TypeHelper.Instance.BaseTypeConstructors.TryGetValue(type, out Func<string, dynamic> ctor))
return (object)ctor.Invoke((string)ctor_arg);
else if (type.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute)))
return Activator.CreateInstance(type, ((IDictionary<string, object>)ctor_arg).Values.ToArray());
else if (typeof(ICollection).IsAssignableFrom(type))
{
var method_As = typeof(TypeHelper).GetMethod("As",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic)
.MakeGenericMethod(type.GetGenericArguments()[0]);
object collection = type.GetConstructor(Type.EmptyTypes).Invoke(null);
var method_Add = type.GetMethod("Add");
Log(method_Add.Name);
foreach (object el in (IEnumerable)ctor_arg)
{
var pel = method_As.Invoke(null, new object[] { el });
method_Add.Invoke(collection, new object[] { pel });
}
return collection;
}
else throw new Exception($"can't create instance of {type.FullName}");
}
Dictionary<string, dynamic> output = new();
Type type;
string name;
object value;
for (; i < text.Length; i++)
{
type = ReadType();
name = ReadName();
bool _ = false;
value = ReadValue(ref _);
output.Add(name, CreateInstance(type, value));
}
return output;
}
// public override void Append(ICollection<KeyValuePair<string, dynamic>> anotherDtsod) => base.Append(anotherDtsod);//UpdateLazy();
public override void Add(string key, dynamic value) => base.Add(key, (object)value);//UpdateLazy();
protected static string Serialize(IDictionary<string, dynamic> dtsod, ushort tabsCount = 0)
{
StringBuilder b = new();
foreach (KeyValuePair<string, dynamic> pair in dtsod)
{
SerializeObject(pair.Key, pair.Value);
}
void SerializeObject(string name, dynamic inst)
{
Type type = inst.GetType();
b.Append(TypeHelper.Instance.TypeToString(type)).Append(':')
.Append(name).Append('=');
if (TypeHelper.Instance.BaseTypeNames.ContainsKey(type))
{
if (type == typeof(decimal) || type == typeof(double) || type == typeof(float))
b.Append(inst.ToString(CultureInfo.InvariantCulture));
else b.Append(inst.ToString());
}
else if (typeof(IDictionary<string, dynamic>).IsAssignableFrom(type))
b.Append("\n{\n").Append(Serialize(inst, tabsCount++)).Append("};\n");
else if (type.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute)))
{
var props = type.GetProperties().Where(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(DtsodSerializableAttribute)));
foreach (var prop in props)
{
var propval = prop.GetValue(inst);
}
}
else throw new Exception($"can't serialize type {type.FullName}");
}
return b.ToString();
}
protected Lazy<string> serialized;
protected void UpdateLazy() => serialized = new(() => Serialize(this));
public override string ToString() => serialized.Value;
}

View File

@@ -1,114 +0,0 @@
namespace DTLib.Dtsod;
public class TypeHelper
{
static Lazy<TypeHelper> _inst = new();
public static TypeHelper Instance => _inst.Value;
internal readonly Dictionary<Type, Func<string, dynamic>> BaseTypeConstructors = new()
{
{ typeof(bool), (inp) => inp.ToBool() },
{ typeof(char), (inp) => inp.ToChar() },
{ typeof(string), (inp) => inp.ToString() },
{ typeof(byte), (inp) => inp.ToByte() },
{ typeof(sbyte), (inp) => inp.ToSByte() },
{ typeof(short), (inp) => inp.ToShort() },
{ typeof(ushort), (inp) => inp.ToUShort() },
{ typeof(int), (inp) => inp.ToInt() },
{ typeof(uint), (inp) => inp.ToUInt() },
{ typeof(long), (inp) => inp.ToLong() },
{ typeof(ulong), (inp) => inp.ToULong() },
{ typeof(float), (inp) => inp.ToFloat() },
{ typeof(double), (inp) => inp.ToDouble() },
{ typeof(decimal), (inp) => inp.ToDecimal() }
};
internal Dictionary<Type, string> BaseTypeNames = new()
{
{ typeof(bool), "bool" },
{ typeof(char), "char" },
{ typeof(string), "string" },
{ typeof(byte), "byte" },
{ typeof(sbyte), "sbyte" },
{ typeof(short), "short" },
{ typeof(ushort), "ushort" },
{ typeof(int), "int" },
{ typeof(uint), "uint" },
{ typeof(long), "long" },
{ typeof(ulong), "ulong" },
{ typeof(float), "float" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" }
};
private Dictionary<string, Type> ST_extensions = new()
{
{ "List<bool>", typeof(List<bool>) },
{ "List<char>", typeof(List<char>) },
{ "List<string>", typeof(List<string>) },
{ "List<byte>", typeof(List<byte>) },
{ "List<sbyte>", typeof(List<sbyte>) },
{ "List<short>", typeof(List<short>) },
{ "List<ushort>", typeof(List<ushort>) },
{ "List<int>", typeof(List<int>) },
{ "List<uint>", typeof(List<uint>) },
{ "List<long>", typeof(List<long>) },
{ "List<ulong>", typeof(List<ulong>) },
{ "List<float>", typeof(List<float>) },
{ "List<double>", typeof(List<double>) },
{ "List<decimal>", typeof(List<decimal>) },
};
private Dictionary<Type, string> TS_extensions = new()
{
{ typeof(List<bool>), "List<bool>" },
{ typeof(List<char>), "List<char>" },
{ typeof(List<string>), "List<string>" },
{ typeof(List<byte>), "List<byte>" },
{ typeof(List<sbyte>), "List<sbyte>" },
{ typeof(List<short>), "List<short>" },
{ typeof(List<ushort>), "List<ushort>" },
{ typeof(List<int>), "List<int>" },
{ typeof(List<uint>), "List<uint>" },
{ typeof(List<long>), "List<long>" },
{ typeof(List<ulong>), "List<ulong>" },
{ typeof(List<float>), "List<float>" },
{ typeof(List<double>), "List<double>" },
{ typeof(List<decimal>), "List<decimal>" },
};
public TypeHelper Extend(string name, Type type)
{
ST_extensions.Add(name, type);
TS_extensions.Add(type, name);
return this;
}
public string TypeToString(Type t) =>
BaseTypeNames.TryGetValue(t, out string name)
? name
: TS_extensions.TryGetValue(t, out name)
? name
: t.FullName;
public Type TypeFromString(string str) => str switch
{
"bool" => typeof(bool),
"char" => typeof(char),
"string" => typeof(string),
"byte" => typeof(byte),
"sbyte" => typeof(sbyte),
"short" => typeof(short),
"ushort" => typeof(ushort),
"int" => typeof(int),
"uint" => typeof(uint),
"long" => typeof(long),
"ulong" => typeof(ulong),
"float" => typeof(float),
"double" => typeof(double),
"decimal" => typeof(decimal),
_ => ST_extensions.TryGetValue(str, out var t)
? t
: Type.GetType(str, false)
?? throw new Exception($"DtsodV30.Deserialize.ParseType() error: type {str} doesn't exists")
};
internal static T As<T>(object inst) where T : class => inst as T;
}

View File

@@ -2,33 +2,32 @@
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Logging.Microsoft</PackageId>
<Version>1.0.0</Version>
<Version>1.1.3</Version>
<Authors>Timerix</Authors>
<Description>DTLib logger wrapper with dependency injection</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/DTLib</RepositoryUrl>
<PackageProjectUrl>https://github.com/Timerix22/DTLib</PackageProjectUrl>
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<TargetFramework>netstandard2.0</TargetFramework>
<!--language features-->
<LangVersion>12</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<!--external dependencies-->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.5" />
</ItemGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib.Logging\DTLib.Logging.csproj" />
<ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib.Logging" Version="1.3.3" />
<PackageReference Include="DTLib" Version="1.7.4" />
</ItemGroup>
</Project>

View File

@@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Logging</PackageId>
<Version>1.3.3</Version>
<Authors>Timerix</Authors>
<Description>Loggers with dependency injection</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/DTLib</RepositoryUrl>
<PackageProjectUrl>https://github.com/Timerix22/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<!--language features-->
<LangVersion>12</LangVersion>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib\DTLib.csproj" />
<ProjectReference Include="..\DTLib.Ben.Demystifier\DTLib.Ben.Demystifier.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.3.3" />
<PackageReference Include="DTLib.Ben.Demystifier" Version="1.0.6" />
</ItemGroup>
</Project>

View File

@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Network</PackageId>
<Version>1.4.0</Version>
<Authors>Timerix</Authors>
<Description>Some sick network protocols</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/DTLib</RepositoryUrl>
<PackageProjectUrl>https://github.com/Timerix22/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<!--language features-->
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib.Dtsod\DTLib.Dtsod.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib.Dtsod" Version="1.3.3" />
</ItemGroup>
</Project>

View File

@@ -1,9 +0,0 @@
using System.ComponentModel;
// включает init и record из c# 9.0
#if !NET6_0 && !NET7_0 && !NET8_0
namespace System.Runtime.CompilerServices;
[EditorBrowsable(EditorBrowsableState.Never)]
public class IsExternalInit { }
#endif

View File

@@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--compilation properties-->
<OutputType>Exe</OutputType>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<!--language features-->
<LangVersion>12</LangVersion>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib.Logging\DTLib.Logging.csproj" />
<ProjectReference Include="..\DTLib.Network\DTLib.Network.csproj" />
<ProjectReference Include="..\DTLib.Dtsod\DTLib.Dtsod.csproj" />
<ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.3.3" />
<PackageReference Include="DTLib.Dtsod" Version="1.3.3" />
<PackageReference Include="DTLib.Network" Version="1.4.0" />
<PackageReference Include="DTLib.Logging" Version="1.3.3" />
</ItemGroup>
<!--project files-->
<ItemGroup>
<None Update="Dtsod\TestResources\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -1,40 +0,0 @@
/*
using KerepWrapper.Autoarr;
using KerepWrapper.KerepTypes;
namespace DTLib.Tests;
public static class TestAutoarr
{
public static void TestAll()
{
var ar = new Autoarr<KVPair>(4, 4, false);
Fill(ar);
Print(ar);
Free(ar);
}
public static void Fill(Autoarr<KVPair> ar)
{
Logger.Log("c", "----------[TestAutoarr/Fill]----------");
for(uint i=0;i<ar.MaxLength;i++)
ar.Add(new KVPair($"key_{i}",new Unitype(i)));
Logger.Log("g", "test completed");
}
public static void Print(Autoarr<KVPair> ar)
{
Logger.Log("c", "----------[TestAutoarr/Print]---------");
foreach (KVPair pair in ar)
Logger.Log("h", pair.ToString());
Logger.Log("g", "test completed");
}
public static void Free(Autoarr<KVPair> ar)
{
Logger.Log("c", "----------[TestAutoarr/Free]----------");
ar.Dispose();
Logger.Log("g", "test completed");
}
}
*/

View File

@@ -1,88 +0,0 @@
using System.Linq;
namespace DTLib.Tests;
public static class TestDtsodV23
{
public static void TestAll()
{
TestBaseTypes();
TestLists();
TestComplexes();
TestReSerialization();
TestSpeed();
TestMemoryConsumption();
}
public static void TestBaseTypes()
{
ColoredConsole.WriteLine("c", "-----[TestDtsodV23/TestBaseTypes]-----");
DtsodV23 dtsod = new(File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "base_types.dtsod")));
foreach (var pair in dtsod)
ColoredConsole.WriteLine("b", pair.Value.GetType().Name + ' ', "w", pair.Key + ' ', "c", pair.Value.ToString());
ColoredConsole.WriteLine("g", "test completed");
}
public static void TestLists()
{
ColoredConsole.WriteLine("c", "-------[TestDtsodV23/TestLists]-------");
DtsodV23 dtsod = new(File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "lists.dtsod")));
foreach (var pair in dtsod)
{
ColoredConsole.WriteLine("b", pair.Value.GetType().Name + ' ', "w", pair.Key, "c",
$" count: {pair.Value.Count}");
foreach (var el in pair.Value)
ColoredConsole.WriteLine("b", '\t'+el.GetType().Name + ' ', "c", el.ToString());
}
ColoredConsole.WriteLine("g", "test completed");
}
public static void TestComplexes()
{
ColoredConsole.WriteLine("c", "-----[TestDtsodV23/TestComplexes]-----");
DtsodV23 dtsod = new(File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "complexes.dtsod")));
foreach (var complex in dtsod)
{
ColoredConsole.WriteLine("b", complex.Value.GetType().Name + ' ', "w", complex.Key,
"b", " size: ", "c", complex.Value.Keys.Count.ToString());
foreach (var pair in (DtsodV23) complex.Value)
ColoredConsole.WriteLine("b", '\t' + pair.Value.GetType().Name + ' ', "w", pair.Key + ' ',
"c", pair.Value.ToString());
}
ColoredConsole.WriteLine("g", "test completed");
}
public static void TestReSerialization()
{
ColoredConsole.WriteLine("c", "--[TestDtsodV23/TestReSerialization]--");
var dtsod = new DtsodV23(new DtsodV23(new DtsodV23(
new DtsodV23(File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "complexes.dtsod")))).ToString()).ToString());
ColoredConsole.WriteLine("y", dtsod.ToString());
ColoredConsole.WriteLine("g", "test completed");
}
public static void TestSpeed()
{
ColoredConsole.WriteLine("c", "-------[TestDtsodV23/TestSpeed]-------");
IDtsod dtsod=null;
string text = File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "messages.dtsod"));
LogOperationTime("V21 deserialization",64,()=>dtsod=new DtsodV21(text));
LogOperationTime("V21 serialization", 64, () => _=dtsod.ToString());
LogOperationTime("V23 deserialization", 64, () => dtsod = new DtsodV23(text));
LogOperationTime("V23 serialization", 64, () => _ = dtsod.ToString());
ColoredConsole.WriteLine("g", "test completed");
}
public static void TestMemoryConsumption()
{
ColoredConsole.WriteLine("c", "----[TestDtsodV23/TestMemConsumpt]----");
string text = File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "messages.dtsod"));
var a = GC.GetTotalMemory(true);
var dtsods = new DtsodV23[64];
for (int i = 0; i < dtsods.Length; i++)
dtsods[i] = new(text);
var b = GC.GetTotalMemory(true);
ColoredConsole.WriteLine("b", "at the start: ","c",$"{a/1024} kb\n",
"b", "at the end: ", "c", $"{b / 1024} kb\n{dtsods.Count()}","b"," dtsods initialized");
ColoredConsole.WriteLine("g", "test completed");
}
}

View File

@@ -1,84 +0,0 @@
/*
using KerepWrapper.Dtsod;
using KerepWrapper.Autoarr;
using KerepWrapper.KerepTypes;
namespace DTLib.Tests;
public static class TestDtsodV24
{
public static void TestAll()
{
TestBaseTypes();
TestComplexes();
TestLists();
TestReSerialization();
TestSpeed();
}
public static void TestBaseTypes()
{
Logger.Log("c", "-----[TestDtsodV24/TestBaseTypes]-----");
DtsodV24 dtsod = new(File.ReadAllText($"Path.Concat("Dtsod","TestResources","DtsodV24", "base_types.dtsod")));
foreach (var pair in dtsod)
Logger.Log("b", pair.ToString());
Logger.Log("g", "test completed");
}
public static void TestComplexes()
{
Logger.Log("c", "-----[TestDtsodV24/TestComplexes]-----");
DtsodV24 dtsod = new(File.ReadAllText($"Path.Concat("Dtsod","TestResources","DtsodV24", "complexes.dtsod")));
Logger.Log("h", dtsod.ToString());
Logger.Log("g", "test completed");
}
public static void TestLists()
{
Logger.Log("c", "-------[TestDtsodV24/TestLists]-------");
DtsodV24 dtsod = new(File.ReadAllText($"Path.Concat("Dtsod","TestResources","DtsodV24", "lists.dtsod")));
foreach (KVPair pair in dtsod)
{
var list = new Autoarr<Unitype>(pair.value.VoidPtr, false);
Logger.Log("b", pair.key.HGlobalUTF8ToString(), "w", $" length: {list.Length}");
foreach (var el in list)
{
Logger.Log("h", '\t' + el.ToString());
if (el.TypeCode == KerepTypeCode.AutoarrUnitypePtr)
{
var ar = new Autoarr<Unitype>(el.VoidPtr, false);
foreach (var k in ar)
{
Logger.Log($"\t\t{k.ToString()}");
}
}
}
}
Logger.Log("g", "test completed");
}
public static void TestReSerialization()
{
Logger.Log("c", "--[TestDtsodV24/TestReSerialization]--");
var dtsod = new DtsodV24(new DtsodV24(new DtsodV24(
new DtsodV24(File.ReadAllText($"Path.Concat("Dtsod","TestResources","DtsodV24", "complexes.dtsod"))).ToString()).ToString()).ToString()));
Logger.Log("h", dtsod.ToString());
Logger.Log("g", "test completed");
}
public static void TestSpeed()
{
Logger.Log("c", "-------[TestDtsodV24/TestSpeed]-------");
IDtsod dtsod=null;
string _text = File.ReadAllText(Path.Concat("Dtsod","TestResources","DtsodV23", "messages.dtsod"));
string text = "";
LogOperationTime( "V23 to V24 conversion", 32, ()=>
text = DtsodConverter.ConvertVersion(new DtsodV23(_text), DtsodVersion.V24).ToString()
);
File.WriteAllText($"Path.Concat("Dtsod","TestResources","DtsodV24", "messages.dtsod",text));
LogOperationTime("V24 deserialization", 64, () => dtsod = new DtsodV24(text));
LogOperationTime("V24 serialization", 64, () => text = dtsod.ToString());
Logger.Log("g", "test completed");
}
}
*/

View File

@@ -1,54 +0,0 @@
/*
using System.Runtime.InteropServices;
using KerepWrapper.Dtsod;
using KerepWrapper.KerepTypes;
namespace DTLib.Tests;
public static class TestPInvoke
{
public static void TestAll()
{
DependencyResolver.CopyLibs();
TestUTF8();
TestPrintf();
TestMarshalling();
}
public static void TestUTF8()
{
Logger.Log("c", "--------[TestPInvoke/TestUTF8]--------", "b", "");
IntPtr ptr;
string str="_$\"\\\\'''\ta ыыы000;2;=:%d;```";
for(int i=0; i<1000; i++)
{
ptr = Unmanaged.StringToHGlobalUTF8(str);
str = Unmanaged.HGlobalUTF8ToString(ptr);
}
Logger.Log("y", str);
}
[DllImport("kerep", CallingConvention = CallingConvention.Cdecl)]
static extern void pinvoke_print([MarshalAs(UnmanagedType.LPStr)] string msg);
public static void TestPrintf()
{
Logger.Log("c", "---------[TestPInvoke/Printf]---------", "b", "");
pinvoke_print("ъъ~ 中文");
Logger.Log("g", "test completed");
}
[DllImport("kerep", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void test_marshalling([MarshalAs(UnmanagedType.LPStr)] string text, out IntPtr kptr);
public static unsafe void TestMarshalling()
{
Logger.Log("c", "---------[TestAutoarr/TestMarshalling]----------");
string msg = "ъъ~ 中文";
test_marshalling(msg, out var kptr);
KVPair k = *(KVPair*)kptr;
Logger.Log("b", k.ToString());
Logger.Log("g", "test completed");
}
}
*/

View File

@@ -1,14 +0,0 @@
bool: false;
char: 'v';
byte: 255b;
sbyte: -125sb;
short: 14003s;
ushort: 32025us;
int: -2515;
uint: 0u;
long: -29863854396l;
ulong: 87659057946ul;
float: 39.944f;
double: 965.557;
decimal: -84.20de;
string: "_$\"\\\\'''\n\ta ыыы000;2;=:%d;```";

View File

@@ -1,9 +0,0 @@
message:
{
type: "sent";
time: "15.12.2021 20:51:24 +03:00";
author_id: 293798876950036480ul;
channel_id: 913088838761603212ul;
message_id: 920734809096077353ul;
text: "_$\"\\\\'''\n\ta ыыы000;2;=:%d;```";
};

View File

@@ -1,5 +0,0 @@
chars: ['a','b','c'];
uints: [10,20,30,0,0];
floats: [8.2,5.225,-0.9993];
strings:["aaa","bbb","ccc"];
things:["aaa",'b',-122];

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
bool: false ;
int: -2515;
uint: 0u;
float: 39.944f;
double: 965.557f;
string: "_$\"\\\\'''\n\ta ыыы000;2;=:%d;```";

View File

@@ -1,10 +0,0 @@
message:
{
type: "sent";
time: "15.12.2021 20:51:24 +03:00";
author_id: -293798876950036480;
channel_id: 913088838761603212;
message_id: 920734809096077353u;
text: "_$\"\\\\'''\n\ta ыыы000;2;=:%d;```";
den: -0.99f;
};

View File

@@ -1,24 +0,0 @@
uints: [10,20,30,0,0 ];
floats: [8.2f,5.225f,-0.9993f];
strings: ["aaa","bbb","ccc"];
things: ["aaa",true,-122];
$complex: {
a:141;
b:00f;
c:false;
};
$complex: {
d:-1u;
e:005;
f:-1f;
};
list_of_lists: [ [ "sss" ] ];
blank_list: [];
qqq: [
{
},
{
q: 0.0f;
}
];

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
bool:b=false;
char:c='v';
string:s="hello";
byte:by=255;
sbyte:sb=-125;
short:sh=14003;
ushort:us=32025;
int:i=-2515;
uint:ui=0;
long:l=-29863854396;
ulong:ul=87659057946;
float:f=39.944;
double:do=965.557;
decimal:de=-84.20;

View File

@@ -1 +0,0 @@
List<string>:list=[string:"a",string:"b"];

View File

@@ -1,48 +0,0 @@
global using System;
global using System.Collections;
global using System.Collections.Generic;
global using System.Text;
global using System.Threading;
global using System.Threading.Tasks;
global using DTLib;
global using DTLib.Extensions;
global using DTLib.Filesystem;
global using DTLib.Dtsod;
global using static DTLib.Tests.TesterLog;
global using static DTLib.Tests.Program;
global using DTLib.Console;
global using DTLib.Logging;
namespace DTLib.Tests;
public static class Program
{
public static ILogger Logger = new CompositeLogger(new ConsoleLogger(),
new FileLogger("logs", "DTLib.Tests"))
{
DebugLogEnabled = true
};
public static void Main(string[] args)
{
System.Console.OutputEncoding = Encoding.UTF8;
System.Console.InputEncoding = Encoding.UTF8;
var mainContext = new ContextLogger("Main", Logger);
try
{
new LaunchArgumentParser().WithNoExit().ParseAndHandle(args);
TestDtsodV23.TestAll();
// TestPInvoke.TestAll();
// TestAutoarr.TestAll();
// TestDtsodV24.TestAll();
}
catch(LaunchArgumentParser.ExitAfterHelpException)
{ }
catch (Exception ex)
{ mainContext.LogError(ex); }
System.Console.ResetColor();
}
}

View File

@@ -1,17 +0,0 @@
using System.Diagnostics;
namespace DTLib.Tests;
public static class TesterLog
{
public static void LogOperationTime(string op_name, int repeats, Action operation)
{
Stopwatch clock = new();
clock.Start();
for (int i = 0; i < repeats; i++)
operation();
clock.Stop();
double time=(double)(clock.ElapsedTicks)/Stopwatch.Frequency/repeats;
Logger.LogInfo(nameof(TesterLog), $"operation {op_name} lasted {time.ToString(MyTimeFormat.ForText)} seconds");
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--package info-->
<PackageId>DTLib.Web</PackageId>
<Version>1.4.0</Version>
<Authors>Timerix</Authors>
<Description>HTTP Server with simple routing</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFramework>netstandard2.0</TargetFramework>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<!--language features-->
<LangVersion>latest</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
</PropertyGroup>
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.7.4" />
</ItemGroup>
</Project>

18
DTLib.Web/HttpMethod.cs Normal file
View File

@@ -0,0 +1,18 @@
namespace DTLib.Web;
/// <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods"/>
[Flags]
public enum HttpMethod : ushort
{
NONE = 0,
GET = 1,
POST = 2,
PUT = 4,
DELETE = 8,
PATCH = 16,
HEAD = 32,
OPTIONS = 64,
TRACE = 128,
CONNECT = 256,
ANY = 65535
}

View File

@@ -0,0 +1,9 @@
namespace DTLib.Web.Routes;
public class DelegateRouteHandler(
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> routeHandler
) : IRouteHandler
{
public Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
=> routeHandler(ctx, requestLogger);
}

View File

@@ -0,0 +1,6 @@
namespace DTLib.Web.Routes;
public interface IRouteHandler
{
Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger);
}

View File

@@ -0,0 +1,6 @@
namespace DTLib.Web.Routes;
public interface IRouter
{
Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger);
}

View File

@@ -0,0 +1,40 @@
namespace DTLib.Web.Routes;
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : IRouteHandler
{
public async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
{
if (ctx.Request.HttpMethod != "GET")
return HttpStatusCode.BadRequest;
string requestPath = ctx.Request.Url?.AbsolutePath!;
if (string.IsNullOrEmpty(requestPath) || requestPath == "/")
{
requestPath = _homePageUrl;
}
string ext = Path.Extension(requestPath).Str;
IOPath filePath = Path.Concat(_publicDir, requestPath);
if (!File.Exists(filePath))
return HttpStatusCode.NotFound;
List<(string key, string val)> headers = ext switch
{
"html" => [("Content-Type", "text/html")],
"css" => [("Content-Type", "text/css")],
"js" or "jsx" or "ts" or "tsx" or "map" => [("Content-Type", "text/javascript")],
_ =>
[
("Content-Type", "binary/octet-stream"),
("Content-Disposition", $"attachment filename={filePath.LastName()}")
]
};
foreach (var header in headers)
ctx.Response.Headers.Set(header.key, header.val);
var fileStream = File.OpenRead(filePath);
ctx.Response.ContentLength64 = fileStream.Length;
await fileStream.CopyToAsync(ctx.Response.OutputStream);
return HttpStatusCode.OK;
}
}

View File

@@ -0,0 +1,65 @@
namespace DTLib.Web.Routes;
public class SimpleRouter : IRouter
{
/// route for any url that doesn't have its own handler
public record RouteWithMethod(HttpMethod method, IRouteHandler routeHandler)
{
public bool CheckMethod(HttpMethod requestMethod) => (requestMethod & method) != 0;
public bool CheckMethod(string requestMethodStr)
=> Enum.TryParse<HttpMethod>(requestMethodStr, out var requestMethod)
&& CheckMethod(requestMethod);
}
private readonly Dictionary<string, RouteWithMethod> _routes = new();
private readonly ILogger _logger;
public RouteWithMethod? DefaultRoute { get; set; }
public SimpleRouter(ILogger logger)
{
_logger = new ContextLogger(nameof(SimpleRouter), logger);
}
public void MapRoute(string url, HttpMethod method, IRouteHandler route)
=> _routes.Add(url, new RouteWithMethod(method, route));
public void MapRoute(string url, HttpMethod method,
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> route)
=> MapRoute(url, method, new DelegateRouteHandler(route));
public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger)
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
try
{
string? requestPath = ctx.Request.Url?.AbsolutePath;
if (string.IsNullOrEmpty(requestPath))
requestPath = "/";
if(!_routes.TryGetValue(requestPath!, out var routeWithMethod))
routeWithMethod = DefaultRoute;
if (routeWithMethod is null)
{
_logger.LogWarn(nameof(SimpleRouter),
$"couldn't resolve request path {ctx.Request.HttpMethod} {requestPath}");
status = HttpStatusCode.NotFound;
}
else if (!routeWithMethod.CheckMethod(ctx.Request.HttpMethod))
{
_logger.LogWarn(nameof(SimpleRouter),
$"received request with invalid method {ctx.Request.HttpMethod} {requestPath}");
status = HttpStatusCode.MethodNotAllowed;
}
else status = await routeWithMethod.routeHandler.HandleRequest(ctx, requestLogger);
}
finally
{
ctx.Response.StatusCode = (int)status;
await ctx.Response.OutputStream.FlushAsync();
ctx.Response.OutputStream.Close();
}
}
}

73
DTLib.Web/WebApp.cs Normal file
View File

@@ -0,0 +1,73 @@
global using System;
global using System.Collections.Generic;
global using System.Text;
global using System.Threading;
global using System.Threading.Tasks;
global using DTLib.Filesystem;
global using DTLib.Logging;
global using System.Net;
using System.Diagnostics;
using DTLib.Extensions;
using DTLib.Web.Routes;
namespace DTLib.Web;
public class WebApp
{
private readonly string _baseUrl;
private readonly ContextLogger _logger;
private readonly IRouter _router;
private readonly CancellationToken _stopToken;
public WebApp(string baseUrl, ILogger logger, IRouter router, CancellationToken stopToken = default)
{
_logger = new ContextLogger(nameof(WebApp), logger);
_baseUrl = baseUrl;
_stopToken = stopToken;
_router = router;
}
public async Task Run()
{
_logger.LogInfo($"starting server at '{_baseUrl}'...");
HttpListener server = new HttpListener();
server.Prefixes.Add(_baseUrl);
server.Start();
_logger.LogInfo("server started");
long requestId = 1;
try
{
while (!_stopToken.IsCancellationRequested)
{
var ctx = await server.GetContextAsync().AsCancellable(_stopToken);
HandleRequestAsync(ctx, new ContextLogger($"Request-{requestId++}", _logger));
}
}
catch (OperationCanceledException)
{}
server.Stop();
_logger.LogInfo("server stopped");
}
private async void HandleRequestAsync(HttpListenerContext ctx, ContextLogger requestLogger)
{
try
{
requestLogger.LogInfo($"{ctx.Request.HttpMethod} {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}...");
var stopwatch = new Stopwatch();
stopwatch.Start();
await _router.Resolve(ctx, requestLogger);
stopwatch.Stop();
requestLogger.LogInfo($"responded {ctx.Response.StatusCode}" +
$" ({(HttpStatusCode)ctx.Response.StatusCode})" +
$" in {stopwatch.ElapsedMilliseconds}ms");
}
catch (Exception ex)
{
requestLogger.LogWarn(ex);
}
}
}

1
DTLib.XXHash Submodule

Submodule DTLib.XXHash added at 3e1a2c00e6

View File

@@ -5,15 +5,7 @@ VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DTLib", "DTLib\DTLib.csproj", "{B620E5E9-800F-4B2D-B4A5-062E05DB704F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DTLib.Tests", "DTLib.Tests\DTLib.Tests.csproj", "{72BA37EF-07EC-4D34-966A-20D5E83ADB32}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Dtsod", "DTLib.Dtsod\DTLib.Dtsod.csproj", "{ADE425F5-8645-47F0-9AA8-33FA748D36BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Network", "DTLib.Network\DTLib.Network.csproj", "{24B7D0A2-0462-424D-B3F5-29A6655FE472}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging", "DTLib.Logging\DTLib.Logging.csproj", "{00B76172-32BB-4B72-9891-47FAEF63386C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Ben.Demystifier", "DTLib.Ben.Demystifier\DTLib.Ben.Demystifier.csproj", "{AC7CB524-4D59-42E0-9F96-1C201A92494B}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Demystifier", "DTLib.Demystifier\DTLib.Demystifier.csproj", "{AC7CB524-4D59-42E0-9F96-1C201A92494B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6308F24E-A4FF-46B3-B72F-30E05DDCB1D5}"
ProjectSection(SolutionItems) = preProject
@@ -26,63 +18,38 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
push_packages.sh = push_packages.sh
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KerepWrapper", "KerepWrapper\KerepWrapper.csproj", "{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging.Microsoft", "DTLib.Logging.Microsoft\DTLib.Logging.Microsoft.csproj", "{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Web", "DTLib.Web\DTLib.Web.csproj", "{9A3220EB-CCED-4172-9BD4-C3700FF36539}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Publish|Any CPU = Publish|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Release|Any CPU.Build.0 = Release|Any CPU
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{B620E5E9-800F-4B2D-B4A5-062E05DB704F}.Publish|Any CPU.Build.0 = Release|Any CPU
{72BA37EF-07EC-4D34-966A-20D5E83ADB32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72BA37EF-07EC-4D34-966A-20D5E83ADB32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72BA37EF-07EC-4D34-966A-20D5E83ADB32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72BA37EF-07EC-4D34-966A-20D5E83ADB32}.Release|Any CPU.Build.0 = Release|Any CPU
{72BA37EF-07EC-4D34-966A-20D5E83ADB32}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Release|Any CPU.Build.0 = Release|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{ADE425F5-8645-47F0-9AA8-33FA748D36BE}.Publish|Any CPU.Build.0 = Release|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Release|Any CPU.Build.0 = Release|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{24B7D0A2-0462-424D-B3F5-29A6655FE472}.Publish|Any CPU.Build.0 = Release|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Release|Any CPU.Build.0 = Release|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{00B76172-32BB-4B72-9891-47FAEF63386C}.Publish|Any CPU.Build.0 = Release|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Release|Any CPU.Build.0 = Release|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{AC7CB524-4D59-42E0-9F96-1C201A92494B}.Publish|Any CPU.Build.0 = Release|Any CPU
{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}.Release|Any CPU.Build.0 = Release|Any CPU
{9ADC4AA1-1DE4-4BB6-AF38-E84C1A142032}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Release|Any CPU.Build.0 = Release|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
{9CCBAFA1-F191-4CEC-A4FA-CFF9F6CA081F}.Publish|Any CPU.Build.0 = Debug|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,86 +1,76 @@
namespace DTLib.Console;
//
// вывод и ввод цветного текста в консоли
// работает медленнее чем хотелось бы
//
public static class ColoredConsole
{
// парсит название цвета в ConsoleColor
public static ConsoleColor ParseColor(string color) => color switch
{
//case "magneta":
"m" => ConsoleColor.Magenta,
//case "green":
"g" => ConsoleColor.Green,
//case "red":
"r" => ConsoleColor.Red,
//case "yellow":
"y" => ConsoleColor.Yellow,
//case "white":
"w" => ConsoleColor.White,
//case "blue":
"b" => ConsoleColor.Blue,
//case "cyan":
"c" => ConsoleColor.Cyan,
//case "h":
"h" or "gray" => ConsoleColor.Gray,
//case "black":
"black" => ConsoleColor.Black,
_ => throw new Exception($"ColoredConsole.ParseColor({color}) error: incorrect color"),
};
public static void Write(ConsoleColor color,string msg)
{
System.Console.ForegroundColor = color;
System.Console.Write(msg);
System.Console.ForegroundColor = ConsoleColor.Gray;
}
public static void Write(string msg) => Write(ConsoleColor.Gray, msg);
public static int Width => System.Console.WindowWidth;
public static int Height => System.Console.WindowHeight;
// вывод цветного текста
public static void Write(params string[] input)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Fg(ConsoleColor color) => System.Console.ForegroundColor = color;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Bg(ConsoleColor color) => System.Console.BackgroundColor = color;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResetColor() => System.Console.ResetColor();
/// <summary>
/// Clears console buffer and resets color
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear()
{
if (input.Length == 1)
{
Write(input[0]);
return;
}
if (input.Length % 2 != 0)
throw new Exception("ColoredConsole.Write() error: every text string must have color string before");
for (ushort i = 0; i < input.Length; i++)
{
System.Console.ForegroundColor = ParseColor(input[i++]);
System.Console.Write(input[i]);
}
System.Console.ForegroundColor = ConsoleColor.Gray;
ResetColor();
System.Console.Clear();
}
public static void WriteLine() => System.Console.WriteLine();
public static void WriteLine(ConsoleColor color,string msg)
public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
{
System.Console.ForegroundColor = color;
System.Console.WriteLine(msg);
System.Console.ForegroundColor = ConsoleColor.Gray;
if(fg != null) Fg(fg.Value);
if(bg != null) Bg(bg.Value);
System.Console.Write(msg);
}
public static void WriteLine(params string[] input)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLine() => System.Console.Write('\n');
public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
{
Write(input);
Write(msg, fg, bg);
WriteLine();
}
// ввод цветного текста
public static string Read(ConsoleColor color)
public static string ReadLine(ConsoleColor? fg = null, ConsoleColor? bg = null)
{
System.Console.ForegroundColor = color;
var r = System.Console.ReadLine();
System.Console.ForegroundColor = ConsoleColor.Gray;
return r;
if(fg != null) Fg(fg.Value);
if(bg != null) Bg(bg.Value);
return System.Console.ReadLine() ?? string.Empty;
}
public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null) =>
WriteLine(c.Multiply(Width - 1), fg, bg);
public static void WriteTitle(string title, char spacing = '-',
string left_framing = "[", string right_framing = "]",
ConsoleColor? fg = null, ConsoleColor? bg = null)
{
int both_spacing_length = Width - title.Length - left_framing.Length - right_framing.Length - 1;
int left_spacing_length = both_spacing_length / 2;
int right_spacing_length = left_spacing_length + both_spacing_length % 2;
var b = new StringBuilder();
if(left_spacing_length > 0)
b.Append(spacing.Multiply(left_spacing_length));
b.Append(left_framing);
b.Append(title);
b.Append(right_framing);
if(right_spacing_length > 0)
b.Append(spacing.Multiply(right_spacing_length));
WriteLine(b.ToString(), fg, bg);
}
public static string Read(string color) => Read(ParseColor(color));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null)
=> WriteTitle(title, ' ', "", "", fg, bg);
}

View File

@@ -1,64 +1,96 @@
namespace DTLib.Console;
#nullable enable
public class LaunchArgument
public record LaunchArgument
{
public string[] Aliases;
public string Description;
protected string? ParamName1;
protected string? ParamName2;
public Action? Handler;
public Action<string>? HandlerWithArg1;
public Action<string, string>? HandlerWithArg2;
public int RequiredArgsCount;
public int Priority;
public struct Param
{
public readonly string Name;
public string? Value { get; internal set; } = null;
public Param(string name)
{
Name = name;
}
}
private LaunchArgument(string[] aliases, string description, int priority)
public readonly string[] Aliases;
public readonly string Description;
public readonly int Priority;
public readonly Param[] Params;
private readonly Action? Handler;
private readonly Action<string>? Handler1Param;
private readonly Action<string, string>? Handler2Params;
public LaunchArgument(string[] aliases, string description,
Action handler, int priority = 0)
{
Aliases = aliases;
Description = description;
Priority = priority;
}
public LaunchArgument(string[] aliases, string description,
Action handler, int priority = 0)
: this(aliases, description, priority)
{
Handler = handler;
RequiredArgsCount = 0;
Params = [];
}
public LaunchArgument(string[] aliases, string description,
Action<string> handler, string paramName1, int priority=0)
: this(aliases, description, priority)
{
HandlerWithArg1 = handler;
ParamName1 = paramName1;
RequiredArgsCount = 1;
Aliases = aliases;
Description = description;
Priority = priority;
Handler1Param = handler;
Params = [
new Param(paramName1)
];
}
public LaunchArgument(string[] aliases, string description,
Action<string, string> handler, string paramName1, string paramName2, int priority=0)
: this(aliases, description, priority)
{
HandlerWithArg2 = handler;
ParamName1 = paramName1;
ParamName2 = paramName2;
RequiredArgsCount = 2;
Aliases = aliases;
Description = description;
Priority = priority;
Handler2Params = handler;
Params = [
new Param(paramName1),
new Param(paramName2),
];
}
public StringBuilder AppendHelpInfo(StringBuilder b)
internal StringBuilder AppendHelpInfo(StringBuilder b)
{
b.Append(Aliases[0]);
for (int i = 1; i < Aliases.Length; i++)
b.Append(", ").Append(Aliases[i]);
if (!string.IsNullOrEmpty(ParamName1))
b.Append(" [").Append(ParamName1).Append("] ");
if (!string.IsNullOrEmpty(ParamName2))
b.Append(" [").Append(ParamName2).Append("] ");
b.Append("- ").Append(Description);
foreach (var param in Params)
b.Append(" [").Append(param.Name).Append("]");
b.Append(" - ").Append(Description);
return b;
}
public override string ToString() =>
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg1 is null}}}";
internal void Handle()
{
switch (Params.Length)
{
default:
throw new ArgumentOutOfRangeException(Params.Length.ToString());
case 0:
Handler!.Invoke();
break;
case 1:
if (Params[0].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
Handler1Param!.Invoke(Params[0].Value!);
break;
case 2:
if (Params[0].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
if (Params[1].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[1] value");
Handler2Params!.Invoke(Params[0].Value!, Params[1].Value!);
break;
}
}
}

View File

@@ -2,31 +2,135 @@ namespace DTLib.Console;
public class LaunchArgumentParser
{
private Dictionary<string, LaunchArgument> argDict = new();
private List<LaunchArgument> argList = new();
public bool ExitIfNoArgs = true;
public string HelpMessageHeader = "USAGE:";
public bool AllowedNoArguments;
public bool AllowedUnknownArguments;
// ReSharper disable once CollectionNeverQueried.Global
public readonly List<string> UnknownArguments = new();
private readonly Dictionary<string, LaunchArgument> argDict = new();
private readonly List<LaunchArgument> argList = new();
public class ExitAfterHelpException : Exception
public LaunchArgumentParser()
{
internal ExitAfterHelpException() : base("your program can use this exception to exit after displaying help message")
{ }
var help = new LaunchArgument(new[] { "h", "help" },
"shows help message", HelpHandler);
Add(help);
var helpArg = new LaunchArgument(new[] { "ha", "helparg" },
"shows help message for specific argument",
HelpArgHandler, "argument");
Add(helpArg);
}
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this() => WithArgs(arguments);
public LaunchArgumentParser(params LaunchArgument[] arguments) : this() => WithArgs(arguments);
public LaunchArgumentParser WithArgs(IEnumerable<LaunchArgument> args)
{
foreach (var arg in args)
Add(arg);
return this;
}
public LaunchArgumentParser WithArgs(params LaunchArgument[] args)
{
foreach (var arg in args)
Add(arg);
return this;
}
public LaunchArgumentParser WithHelpMessageHeader(string header)
{
HelpMessageHeader = header;
return this;
}
public LaunchArgumentParser AllowNoArguments()
{
AllowedNoArguments = true;
return this;
}
public LaunchArgumentParser AllowUnknownArguments()
{
AllowedUnknownArguments = true;
return this;
}
public string CreateHelpMessage()
{
StringBuilder b = new();
foreach (var arg in argList)
arg.AppendHelpInfo(b).Append('\n');
b.Remove(b.Length-1, 1);
StringBuilder b = new(HelpMessageHeader);
foreach (var arg in argList)
{
b.Append('\n');
arg.AppendHelpInfo(b);
}
return b.ToString();
}
public string CreateHelpArgMessage(string argAlias)
{
StringBuilder b = new();
var arg = Parse(argAlias);
if(!TryParseArg(argAlias, out var arg))
throw new Exception($"unknown argument '{argAlias}'");
arg.AppendHelpInfo(b);
return b.ToString();
}
public void Add(LaunchArgument arg)
{
argList.Add(arg);
foreach (string alias in arg.Aliases)
argDict.Add(alias, arg);
}
public bool TryParseArg(string argAlias, out LaunchArgument arg)
{
// different argument providing patterns
arg = null!;
return argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg) || // --arg
argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg) || // -arg
argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg); // /arg
}
/// <param name="args">program launch args</param>
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
public void ParseAndHandle(string[] args)
{
// show help message and throw ExitAfterHelpException
if (args.Length == 0 && !AllowedNoArguments)
HelpHandler();
List<LaunchArgument> execQueue = new();
for (int i = 0; i < args.Length; i++)
{
if (!TryParseArg(args[i], out var arg))
{
if (!AllowedUnknownArguments)
throw new Exception($"unknown argument '{args[i]}'");
UnknownArguments.Add(args[i]);
}
for (int j = 0; j < arg.Params.Length; j++)
{
if (++i >= args.Length)
throw new Exception(
$"argument '{arg.Aliases[0]}' should have parameter '{arg.Params[j]}' after it");
arg.Params[j].Value = args[i];
}
execQueue.Add(arg);
}
// ascending sort by priority
execQueue.Sort((a0, a1) => a0.Priority - a1.Priority);
// finally executing handlers
foreach (var a in execQueue)
a.Handle();
}
private void HelpHandler()
{
System.Console.WriteLine(CreateHelpMessage());
@@ -39,104 +143,11 @@ public class LaunchArgumentParser
throw new ExitAfterHelpException();
}
public LaunchArgumentParser()
public class ExitAfterHelpException : Exception
{
var help = new LaunchArgument(new[] { "h", "help" },
"shows help message", HelpHandler);
Add(help);
var helpArg = new LaunchArgument( new[]{ "ha", "helparg" },
"shows help message for particular argument",
HelpArgHandler, "argAlias");
Add(helpArg);
}
public LaunchArgumentParser WithNoExit()
{
ExitIfNoArgs = false;
return this;
}
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this()
{
foreach (var arg in arguments)
Add(arg);
}
public LaunchArgumentParser(params LaunchArgument[] arguments) : this()
{
for (var i = 0; i < arguments.Length; i++)
Add(arguments[i]);
}
public void Add(LaunchArgument arg)
{
argList.Add(arg);
for(int a=0; a<arg.Aliases.Length; a++)
argDict.Add(arg.Aliases[a], arg);
}
public LaunchArgument Parse(string argAlias)
{
// different argument providing patterns
if (!argDict.TryGetValue(argAlias, out var arg) && // arg
!(argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg)) && // --arg
!(argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg)) && // -arg
!(argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg))) // /arg
throw new Exception($"invalid argument: {argAlias}\n{CreateHelpMessage()}");
return arg;
}
/// <param name="args">program launch args</param>
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
public void ParseAndHandle(string[] args)
{
// show help and throw
if (args.Length == 0 && ExitIfNoArgs)
HelpHandler();
List<LaunchArgument> execQueue = new();
for (int i = 0; i < args.Length; i++)
public ExitAfterHelpException()
: base("your program can use this exception to exit after displaying help message")
{
LaunchArgument arg = Parse(args[i]);
switch (arg.RequiredArgsCount)
{
case 0:
if (arg.Handler is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
break;
case 1:
{
if (arg.HandlerWithArg1 is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
if (i + 1 >= args.Length)
throw new Exception($"argument <{args[i]}> should have a parameter after it");
string arg1 = args[++i];
arg.Handler = () => arg.HandlerWithArg1(arg1);
break;
}
case 2:
{
if (arg.HandlerWithArg2 is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
if (i + 2 >= args.Length)
throw new Exception($"argument <{args[i]}> should have two params after it");
string arg1 = args[++i], arg2 = args[++i];
arg.Handler = () => arg.HandlerWithArg2(arg1, arg2);
break;
}
}
execQueue.Add(arg);
}
// ascending sort by priority
execQueue.Sort((a0, a1) => a0.Priority-a1.Priority);
// finally executing handlers
foreach (var a in execQueue)
a.Handler!();
}
}

View File

@@ -2,30 +2,35 @@
<PropertyGroup>
<!--package info-->
<PackageId>DTLib</PackageId>
<Version>1.3.3</Version>
<Version>1.7.4</Version>
<Authors>Timerix</Authors>
<Description>Library for all my C# projects</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/DTLib</RepositoryUrl>
<PackageProjectUrl>https://github.com/Timerix22/DTLib</PackageProjectUrl>
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
<Configuration>Release</Configuration>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<!--language features-->
<LangVersion>12</LangVersion>
<LangVersion>latest</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!--project files-->
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<Compile Remove="Experimental\**" />
<!--external dependencies-->
<ItemGroup >
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Experimental\ConsoleGUI\**" />
<!--DTLib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib.Demystifier\DTLib.Demystifier.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib.Demystifier" Version="1.1.1" />
</ItemGroup>
</Project>

View File

@@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=console/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>

View File

@@ -5,13 +5,7 @@ public static class DtsodConverter
public static IDtsod ConvertVersion(IDtsod src, DtsodVersion targetVersion)
=> targetVersion switch
{
DtsodVersion.V21 => new DtsodV21(src.ToDictionary()),
DtsodVersion.V22 => throw new Exception("DtsodV22 is deprecated"),
DtsodVersion.V23 => new DtsodV23(src.ToDictionary()),
// DtsodVersion.V24 => new DtsodV24(src.ToDictionary()),
#if DEBUG
//DtsodVersion.V30 => new DtsodV30(src.ToDictionary()),
#endif
_ => throw new Exception($"DtsodConverter.Convert() error: unknown target version <{targetVersion}>"),
};

View File

@@ -1,6 +1,6 @@
namespace DTLib.Dtsod;
public abstract class DtsodDict<TKey, TVal> : IDictionary<TKey, TVal>, IDictionary
public abstract class DtsodDict<TKey, TVal> : IDictionary<TKey, TVal>, IDictionary where TKey : notnull
{
// да, вместо собственной реализации интерфейса это ссылки на Dictionary
readonly Dictionary<TKey, TVal> baseDict;
@@ -18,7 +18,7 @@ public abstract class DtsodDict<TKey, TVal> : IDictionary<TKey, TVal>, IDictiona
}
}
public virtual bool TryGetValue(TKey key, out TVal value) => baseDict.TryGetValue(key, out value);
public virtual bool TryGetValue(TKey key, out TVal value) => baseDict.TryGetValue(key, out value!);
public bool TrySetValue(TKey key, TVal value)
{
@@ -34,7 +34,7 @@ public abstract class DtsodDict<TKey, TVal> : IDictionary<TKey, TVal>, IDictiona
public virtual void Add(KeyValuePair<TKey, TVal> pair)
=> ((ICollection<KeyValuePair<TKey, TVal>>)baseDict).Add(pair);
public void Add(object key, object value)
public void Add(object key, object? value)
{
((IDictionary) baseDict).Add(key, value);
}
@@ -62,7 +62,7 @@ public abstract class DtsodDict<TKey, TVal> : IDictionary<TKey, TVal>, IDictiona
public ICollection<TVal> Values => baseDict.Values;
public bool IsReadOnly { get; } = false;
public object this[object key]
public object? this[object key]
{
get => ((IDictionary) baseDict)[key];
set => ((IDictionary) baseDict)[key] = value;

View File

@@ -29,7 +29,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
{
string name = ReadName();
if (name.IsNullOrEmpty()) goto end;
dynamic value = ReadValue(out bool _);
var value = ReadValue(out bool _);
if (partOfDollarList)
{
if (!output.TryGetValue(name, out var dollarList))
@@ -85,7 +85,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
: throw new Exception("DtsodV23.Deserialize.ReadName() error: end of text\ntext:\n" + _text);
}
dynamic ReadValue(out bool endOfList)
dynamic? ReadValue(out bool endOfList)
{
endOfList = false;
@@ -160,7 +160,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
return list;
}
dynamic ParseValue(string value_str)
dynamic? ParseValue(string value_str)
{
switch (value_str)
{
@@ -168,7 +168,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
case "false":
return value_str.ToBool();
case "null":
return null;
return null!;
default:
if (value_str.Contains('"'))
return value_str.Substring(1, value_str.Length - 2).Replace("\\\\","\\").Replace("\\\"","\"");
@@ -220,7 +220,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
}
}
object value = null;
object? value = null;
while (++i < text.Length)
{
c = text[i];
@@ -299,7 +299,7 @@ public class DtsodV23 : DtsodDict<string, dynamic>, IDtsod
{ typeof(decimal), (val, b) => b.Append(val.ToString(CultureInfo.InvariantCulture)).Append("de") }
};
protected StringBuilder Serialize(DtsodV23 dtsod, ref int tabscount, StringBuilder b = null)
protected StringBuilder Serialize(DtsodV23 dtsod, ref int tabscount, StringBuilder? b = null)
{
tabscount++;
b ??= new StringBuilder();

View File

@@ -0,0 +1,6 @@
namespace DTLib.Dtsod;
public enum DtsodVersion : byte
{
V23 = 23
}

View File

@@ -1,70 +0,0 @@
using System.Threading;
using DTLib.Console;
namespace DTLib.Experimental
{
public class CompressedArray
{
public class Array1D<T> where T : IComparable<T>
{
byte[] Description;
T[] Memory;
public Array1D() { }
public Array1D(T[] sourceArray) => CompressArray(sourceArray);
public void CompressArray(T[] sourceArray)
{
var listMem = new List<T>();
var listDesc = new List<byte>();
T prevElement = sourceArray[0];
listMem.Add(sourceArray[0]);
listDesc.Add(1);
byte repeats = 1;
for (int i = 1; i < sourceArray.Length; i++)
{
if (prevElement.CompareTo(sourceArray[i]) == 0)
repeats++;
else
{
listMem.Add(sourceArray[i]);
listDesc.Add(1);
if (repeats > 1)
{
listDesc[listDesc.Count - 2] = repeats;
repeats = 1;
}
}
prevElement = sourceArray[i];
}
Memory = listMem.ToArray();
Description = listDesc.ToArray();
ColoredConsole.Write("b", "listMem.Count: ", "c", listMem.Count.ToString(), "b", " listDesc.Count: ", "c",
$"{listDesc.Count}\n");
for (short i = 0; i < listDesc.Count; i++)
{
ColoredConsole.Write("y", $"{Description[i]}:{Memory[i]}\n");
}
}
// блокирует обращение к памяти из нескольких потоков
Mutex storageUsing = new();
// возвращает элемент по индексу так, как если бы шло обращение к обычном массиву
public T GetElement(int index)
{
storageUsing.WaitOne();
T output = default;
int sum = 0;
for (int i = 0; i < Description.Length; i++)
{
if (sum < index)
sum += Description[i];
else output = sum == index ? Memory[i] : Memory[i - 1];
}
storageUsing.ReleaseMutex();
return output;
}
}
}
}

View File

@@ -1,69 +0,0 @@
using DTLib.Dtsod;
using DTLib.Filesystem;
using System.Collections.Generic;
using static DTLib.PublicLog;
namespace DTLib.ConsoleGUI
{
public class Container : List<IDrawable>, IDrawable
{
public (ushort x, ushort y) AnchorPoint { get; set; }
public ushort Width { get; set; }
public ushort Height { get; set; }
public char[] Textmap { get; private set; }
public char[] Colormap { get; private set; }
public string Name { get; private set; }
public Container(string name, string layout_file)
{
Name=name;
ParseLayoutFile(layout_file);
}
void ParseLayoutFile(string layout_file)
{
DtsodV22 layout = new(File.ReadAllText(layout_file));
AnchorPoint=(layout[Name]["anchor"][0], layout[Name]["anchor"][1]);
Width=layout[Name]["width"];
Height=layout[Name]["height"];
foreach(string element_name in layout[Name]["children"].Keys)
{
switch(layout[Name]["children"][element_name]["type"])
{
case "label":
Add(new Label(element_name,
layout[Name]["children"][element_name]["resdir"]+$"\\{element_name}.textmap",
layout[Name]["children"][element_name]["resdir"]+$"\\{element_name}.colormap")
{
AnchorPoint=(layout[Name]["children"][element_name]["anchor"][0],
layout[Name]["children"][element_name]["anchor"][1])
});
break;
}
}
}
public void GenTextmap()
{
Textmap=new char[Width*Height];
for(int i = 0; i<Textmap.Length; i++)
Textmap[i]=' ';
foreach(IDrawable element in this)
{
element.GenTextmap();
Log("m", $"Length: {element.Textmap.Length} calculated: {element.Width*element.Height}\n");
for(ushort y = 0; y<element.Height; y++)
for(ushort x = 0; x<element.Width; x++)
{
//Textmap[(element.AnchorPoint.y + y) * Width + element.AnchorPoint.x + x] = element.Textmap[y * element.Width + x];
Textmap[(y)*Width+x]=element.Textmap[y*element.Width+x];
}
}
}
public void GenColormap()
{
}
}
}

View File

@@ -1,16 +0,0 @@
namespace DTLib.ConsoleGUI
{
public class Control : Label
{
public new void GenColormap()
{
}
public new void GenTextmap()
{
}
}
}

View File

@@ -1,17 +0,0 @@
namespace DTLib.ConsoleGUI
{
public interface IDrawable
{
public (ushort x, ushort y) AnchorPoint { get; set; }
public ushort Width { get; }
public ushort Height { get; }
public char[] Textmap { get; }
public char[] Colormap { get; }
public string Name { get; }
public void GenTextmap();
public void GenColormap();
}
}

View File

@@ -1,35 +0,0 @@
using DTLib.Filesystem;
namespace DTLib.ConsoleGUI
{
public class Label : IDrawable
{
public (ushort x, ushort y) AnchorPoint { get; set; } = (0, 0);
public ushort Width { get; private set; }
public ushort Height { get; private set; }
public char[] Textmap { get; private set; }
public char[] Colormap { get; private set; }
public string TextmapFile { get; set; }
public string ColormapFile { get; set; }
public string Name { get; init; }
public Label() { }
public Label(string name, string textmapFile, string colormapFile)
{
TextmapFile=textmapFile;
ColormapFile=colormapFile;
Name=name;
}
public void GenColormap() => Colormap=File.ReadAllText(ColormapFile).ToCharArray();
public void GenTextmap()
{
Textmap=File.ReadAllText(TextmapFile).ToCharArray();
Width=12;
Height=3;
}
}
}

View File

@@ -1,36 +0,0 @@
using DTLib.Filesystem;
using System;
using System.Text;
namespace DTLib.ConsoleGUI
{
//
// создание gui из текста в консоли
//
public class Window : Container
{
public Window(string layout_file) : base("window", layout_file)
{
Console.Clear();
Console.SetWindowSize(Width+1, Height+1);
Console.SetBufferSize(Width+1, Height+1);
Console.OutputEncoding=Encoding.Unicode;
Console.InputEncoding=Encoding.Unicode;
Console.CursorVisible=false;
}
// выводит все символы
public void RenderFile(string file)
{
Console.Clear();
Console.WriteLine(File.ReadAllText(file));
}
public void Render()
{
GenTextmap();
Console.WriteLine(SimpleConverter.MergeToString(Textmap));
}
}
}

View File

@@ -1,186 +0,0 @@
using DTLib.Filesystem;
using System;
using System.Collections.Generic;
using System.Text;
namespace DTLib.ConsoleGUI
{
//
// создание gui из текста в консоли
//
public class WindowOld
{
public int WindowWidth { get; private set; }
public int WindowHeight { get; private set; }
public char[,] Text;
public char[,] nowText;
public char[,] TextColors;
public char[,] nowTextColors;
public Container WindowContainer;
public WindowOld(int windowWidth, int windowHeight)
{
WindowWidth=windowWidth;
WindowHeight=windowHeight;
Text=new char[windowWidth, windowHeight];
TextColors=new char[windowWidth, windowHeight];
nowText=TextColors;
nowTextColors=new char[windowWidth, windowHeight];
Console.WindowWidth=WindowWidth+1;
Console.WindowHeight=WindowHeight+1;
Console.BufferWidth=WindowWidth+1;
Console.BufferHeight=WindowHeight+1;
Console.OutputEncoding=Encoding.Unicode;
Console.InputEncoding=Encoding.Unicode;
Console.CursorVisible=false;
// заполнение массивов
for(sbyte y = 0; y<WindowHeight; y++)
{
for(sbyte x = 0; x<WindowWidth; x++)
{
Text[x, y]=' ';
TextColors[x, y]='w';
}
}
nowText=TextColors;
}
/*// считывает массив символов из файла
// ширина и высота текста должны быть как указанные при инициализации объекта этого класса
public void ReadFromFile(string path)
{
var r = new StreamReader(path, SimpleConverter.UTF8);
char[] s = new char[1];
// считывание текста
sbyte y = 0, x = 0;
r.Read(s, 0, 1);
while (!r.EndOfStream && y < WindowHeight)
{
if (x == WindowWidth)
{
r.Read(s, 0, 1);
x = 0;
y++;
}
else
{
Text[x, y] = s[0];
x++;
}
r.Read(s, 0, 1);
}
r.Read(s, 0, 1);
// считывание цвета
// если не находит цвет в файле, оставляет старый
if (s[0] == '\n')
{
r.Read(s, 0, 1);
y = 0;
x = 0;
while (!r.EndOfStream && y < WindowHeight)
{
if (x == WindowWidth)
{
r.Read(s, 0, 1);
x = 0;
y++;
}
else
{
TextColors[x, y] = s[0];
x++;
}
r.Read(s, 0, 1);
}
}
r.Close();
}*/
public void ResetCursor() => Console.SetCursorPosition(0, WindowHeight);
// заменяет символ выведенный, использовать после ShowAll()
public void ChangeChar(sbyte x, sbyte y, char ch)
{
Text[x, y]=ch;
nowText[x, y]=ch;
Console.SetCursorPosition(x, y);
ColoredConsole.Write(TextColors[x, y].ToString(), ch.ToString());
}
public void ChangeColor(sbyte x, sbyte y, char color)
{
TextColors[x, y]=color;
nowTextColors[x, y]=color;
Console.SetCursorPosition(x, y);
ColoredConsole.Write(color.ToString(), Text[x, y].ToString());
}
public void ChangeCharAndColor(sbyte x, sbyte y, char color, char ch)
{
Text[x, y]=ch;
nowText[x, y]=ch;
TextColors[x, y]=color;
nowTextColors[x, y]=color;
Console.SetCursorPosition(x, y);
ColoredConsole.Write(color.ToString(), ch.ToString());
}
public void ChangeLine(sbyte x, sbyte y, char color, string line)
{
Console.SetCursorPosition(x, y);
for(sbyte i = 0; i<line.Length; i++)
{
Text[x+i, y]=line[i];
nowText[x+i, y]=line[i];
TextColors[x+i, y]=color;
nowTextColors[x+i, y]=color;
}
ColoredConsole.Write(color.ToString(), line);
}
// выводит все символы
public void ShowAll()
{
var l = new List<string>();
for(sbyte y = 0; y<WindowHeight; y++)
{
for(sbyte x = 0; x<WindowWidth; x++)
{
l.Add(TextColors[x, y].ToString());
l.Add(Text[x, y].ToString());
nowText[x, y]=Text[x, y];
nowTextColors[x, y]=TextColors[x, y];
}
l.Add("w");
l.Add("\n");
}
ColoredConsole.Write(l.ToArray());
//Console.WriteLine();
}
public void UpdateAll()
{
for(sbyte y = 0; y<WindowHeight; y++)
{
for(sbyte x = 0; x<WindowWidth; x++)
{
Console.SetCursorPosition(x, y);
if(TextColors[x, y]!=nowTextColors[x, y]||Text[x, y]!=nowText[x, y])
{
ColoredConsole.Write(TextColors[x, y].ToString(), Text[x, y].ToString());
nowText[x, y]=Text[x, y];
nowTextColors[x, y]=TextColors[x, y];
}
}
Console.Write('\n');
}
}
public void RenderFile(string file)
{
Console.Clear();
Console.WriteLine(File.ReadAllText(file));
}
}
}

View File

@@ -1,8 +0,0 @@
namespace DTLib.Experimental;
// по идее это нужно, чтоб делать так: SomeEvent?.Invoke().Wait()
public delegate Task EventHandlerAsyncDelegate();
public delegate Task EventHandlerAsyncDelegate<T>(T e);
public delegate Task EventHandlerAsyncDelegate<T0, T1>(T0 e0, T1 e1);
public delegate Task EventHandlerAsyncDelegate<T0, T1, T2>(T0 e0, T1 e1, T2 e2);
public delegate Task EventHandlerAsyncDelegate<T0, T1, T2, T3>(T0 e0, T1 e1, T2 e2, T3 e3);

View File

@@ -1,45 +0,0 @@
using System.IO;
namespace DTLib.Experimental;
public class FileInstance
{
public enum FileOpenMode
{
// open a file for reading
Read=1,
// (re)create a file for writing
Write=2,
// opens file for writing additional data to the end / creates new file
Append=4,
// (re)creates file for reading/writing
ReadWrite=Read|Write,
// opens file for readng/writing additional data to the end / creates new file
ReadAppend=Read|Append
}
public readonly FileOpenMode Mode;
public readonly FileStream Stream;
public string Name;
public FileInstance(string path, FileOpenMode mode)
{
if (path.IsNullOrEmpty())
throw new NullReferenceException("path is null");
if(!System.IO.File.Exists(path))
{
if (mode == FileOpenMode.Read)
throw new Exception($"file <{path}> is not found");
}
Mode = mode;
Stream = mode switch
{
FileOpenMode.Read => System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
FileOpenMode.Write => System.IO.File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite),
FileOpenMode.Append => System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite),
FileOpenMode.ReadWrite => System.IO.File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite),
FileOpenMode.ReadAppend => System.IO.File.Open(path, FileMode.Append, FileAccess.ReadWrite, FileShare.ReadWrite),
_ => throw new Exception($"unknown file mode: {mode}")
};
}
}

View File

@@ -1,96 +0,0 @@
namespace DTLib.Experimental.Reactive
{
public class ReactiveListener<T> : ReactiveProvider<T>
{
public ReactiveListener() { }
public ReactiveListener(ReactiveStream<T> stream) : base(stream) { }
public ReactiveListener(ICollection<ReactiveStream<T>> streams) : base(streams) { }
public event Action<ReactiveStream<T>, T> ElementAddedEvent;
void ElementAdded(ReactiveStream<T> stream, TimeSignedObject<T> e) => ElementAdded(stream, e.Value);
void ElementAdded(ReactiveStream<T> stream, T e) =>
Task.Run(() => ElementAddedEvent?.Invoke(stream, e));
public override void Join(ReactiveStream<T> stream)
{
base.Join(stream);
lock (Streams) stream.ElementAddedEvent += ElementAdded;
}
public override void Leave(ReactiveStream<T> stream)
{
base.Leave(stream);
lock (Streams) stream.ElementAddedEvent -= ElementAdded;
}
public T GetFirst()
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
TimeSignedObject<T> rezult = null;
foreach (ReactiveStream<T> stream in Streams)
if (stream.Count != 0)
{
TimeSignedObject<T> e = stream[0];
if (rezult is null) rezult = e;
else if (rezult.Time > e.Time) rezult = e;
}
return rezult.Value;
}
public T GetLast()
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
TimeSignedObject<T> rezult = null;
foreach (ReactiveStream<T> stream in Streams)
if (stream.Count != 0)
{
TimeSignedObject<T> e = stream[stream.Count - 1];
if (rezult is null) rezult = e;
else if (rezult.Time < e.Time) rezult = e;
}
return rezult.Value;
}
public T FindOne(Func<T, bool> condition)
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
foreach (ReactiveStream<T> stream in Streams)
foreach (TimeSignedObject<T> el in stream)
if (condition(el.Value))
return el.Value;
return default;
}
public TimeSignedObject<T> FindOne(Func<TimeSignedObject<T>, bool> condition)
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
foreach (ReactiveStream<T> stream in Streams)
foreach (TimeSignedObject<T> el in stream)
if (condition(el))
return el;
return default;
}
public List<T> FindAll(Func<T, bool> condition)
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
List<T> rezults = new();
foreach (ReactiveStream<T> stream in Streams)
foreach (TimeSignedObject<T> el in stream)
if (condition(el.Value))
rezults.Add(el.Value);
return rezults;
}
public List<TimeSignedObject<T>> FindAll(Func<TimeSignedObject<T>, bool> condition)
{
if (Streams.Count == 0) throw new Exception("ReactiveListener is not connected to any streams");
List<TimeSignedObject<T>> rezults = new();
foreach (ReactiveStream<T> stream in Streams)
foreach (TimeSignedObject<T> el in stream)
if (condition(el))
rezults.Add(el);
return rezults;
}
}
}

View File

@@ -1,31 +0,0 @@
namespace DTLib.Experimental.Reactive
{
public abstract class ReactiveProvider<T>
{
protected List<ReactiveStream<T>> Streams
{
get
{ lock (_streams) return _streams; }
set
{ lock (_streams) _streams = value; }
}
private List<ReactiveStream<T>> _streams = new();
public ReactiveProvider() { }
public ReactiveProvider(ReactiveStream<T> stream) => Streams.Add(stream);
public ReactiveProvider(ICollection<ReactiveStream<T>> streams) => Streams = streams.ToList();
public virtual void Join(ReactiveStream<T> stream)
{
if (IsConnetcedTo(stream)) throw new Exception("ReactiveListener is already connected to the stream");
Streams.Add(stream);
}
public virtual void Leave(ReactiveStream<T> stream)
{
if (!Streams.Remove(stream)) throw new Exception("ReactiveListener is not connected to the stream");
}
public bool IsConnetcedTo(ReactiveStream<T> stream) => Streams.Contains(stream);
}
}

View File

@@ -1,16 +0,0 @@
namespace DTLib.Experimental.Reactive
{
public class ReactiveSender<T> : ReactiveProvider<T>
{
public ReactiveSender() { }
public ReactiveSender(ReactiveStream<T> stream) : base(stream) { }
public ReactiveSender(ICollection<ReactiveStream<T>> streams) : base(streams) { }
public void Send(T e)
{
foreach (ReactiveStream<T> s in Streams)
s.Add(e);
}
}
}

View File

@@ -1,68 +0,0 @@
namespace DTLib.Experimental.Reactive
{
public class ReactiveStream<T> : IList<TimeSignedObject<T>>
{
List<TimeSignedObject<T>> _storage = new();
List<TimeSignedObject<T>> Storage
{
get
{ lock (_storage) return _storage; }
}
public int Count => Storage.Count;
public TimeSignedObject<T> this[int index]
{
get => Storage[index];
set => throw new NotImplementedException();
}
public event Action<ReactiveStream<T>, TimeSignedObject<T>> ElementAddedEvent;
public void Add(TimeSignedObject<T> elem)
{
Storage.Add(elem);
ElementAddedEvent?.Invoke(this, elem);
}
public void Add(T elem) => Add(new TimeSignedObject<T>(elem));
public void Clear() => Storage.Clear();
public int IndexOf(TimeSignedObject<T> item) => Storage.IndexOf(item);
public bool Contains(TimeSignedObject<T> item) => Storage.Contains(item);
public IEnumerator<TimeSignedObject<T>> GetEnumerator() => new Enumerator(Storage);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(Storage);
struct Enumerator : IEnumerator<TimeSignedObject<T>>
{
public Enumerator(List<TimeSignedObject<T>> storage)
{
_storage = storage;
_index = storage.Count - 1;
}
List<TimeSignedObject<T>> _storage;
int _index;
public TimeSignedObject<T> Current => _storage[_index];
object IEnumerator.Current => Current;
public void Dispose() => _storage = null;
public bool MoveNext()
{
if (_index < 0)
return false;
_index--;
return true;
}
public void Reset() => _index = _storage.Count - 1;
}
bool ICollection<TimeSignedObject<T>>.IsReadOnly { get; } = false;
public void Insert(int index, TimeSignedObject<T> item) => throw new NotImplementedException();
public void RemoveAt(int index) => throw new NotImplementedException();
public void CopyTo(TimeSignedObject<T>[] array, int arrayIndex) => throw new NotImplementedException();
public bool Remove(TimeSignedObject<T> item) => throw new NotImplementedException();
}
}

View File

@@ -1,14 +0,0 @@
namespace DTLib.Experimental.Reactive
{
public class TimeSignedObject<T>
{
public T Value { get; init; }
public long Time { get; init; }
public TimeSignedObject(T value)
{
Value = value;
Time = DateTime.Now.Ticks;
}
}
}

View File

@@ -1,35 +0,0 @@
namespace DTLib.Experimental
{
//
// Вычисление псевдослучайного числа из множества параметров.
// Работает медленнее чем класс System.Random, но выдаёт более случайные значения
//
public class SecureRandom
{
/*private RNGCryptoServiceProvider crypt = new();
// получение массива случайных байтов
public byte[] GenBytes(uint length)
{
byte[] output = new byte[length];
crypt.GetNonZeroBytes(output);
return output;
}
// получение случайного числа от 0 до 2147483647
public int NextInt(uint from, int to)
{
int output = 0;
int rez = 0;
while (true)
{
rez = output * 10 + NextBytes(1)[0];
if (rez < to && rez > from)
{
output = rez;
return output;
}
}
}*/
}
}

View File

@@ -1,9 +0,0 @@
using System.ComponentModel;
// включает init и record из c# 9.0
#if !NET6_0 && !NET7_0 && !NET8_0
namespace System.Runtime.CompilerServices;
[EditorBrowsable(EditorBrowsableState.Never)]
public class IsExternalInit { }
#endif

View File

@@ -1,4 +1,4 @@
#if NETSTANDARD2_1 || NET6_0 || NET7_0 || NET8_0
#if NETSTANDARD2_1
namespace DTLib.Extensions;
public static class SpanHelper

View File

@@ -0,0 +1,37 @@
namespace DTLib.Extensions;
public static class TaskExtensions
{
// https://stackoverflow.com/a/69861689
public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
{
if (!token.CanBeCanceled)
{
return task;
}
var tcs = new TaskCompletionSource<T>();
// This cancels the returned task:
// 1. If the token has been canceled, it cancels the TCS straightaway
// 2. Otherwise, it attempts to cancel the TCS whenever
// the token indicates cancelled
token.Register(() => tcs.TrySetCanceled(token),
useSynchronizationContext: false);
task.ContinueWith(t =>
{
// Complete the TCS per task status
// If the TCS has been cancelled, this continuation does nothing
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(t.Exception!);
else tcs.TrySetResult(t.Result);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
return tcs.Task;
}
}

View File

@@ -8,17 +8,18 @@ public static class Directory
public static void Create(IOPath dir)
{
if (Exists(dir)) return;
// creation of parent directories
if (dir.Contains(Path.Sep))
{
var parentDir = dir.ParentDir();
if(!Exists(parentDir))
var parentDir = dir.ParentDir();
if (!Exists(parentDir))
Create(parentDir);
}
System.IO.Directory.CreateDirectory(dir.Str);
}
/// копирует все файлы и папки
public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite)
{
@@ -32,7 +33,7 @@ public static class Directory
Copy_internal(sourceDir, newDir, owerwrite, conflicts);
}
private static void Copy_internal(IOPath sourceDir, IOPath newDir, bool owerwrite, List<IOPath> conflicts)
private static void Copy_internal(IOPath sourceDir, IOPath newDir, bool owerwrite, List<IOPath>? conflicts)
{
bool countConflicts = conflicts is not null;
List<IOPath> files = GetAllFiles(sourceDir);
@@ -41,7 +42,7 @@ public static class Directory
{
var newfile = files[i].ReplaceBase(sourceDir, newDir);
if (countConflicts && File.Exists(newfile))
conflicts.Add(newfile);
conflicts!.Add(newfile);
File.Copy(files[i], newfile, owerwrite);
}
}
@@ -54,24 +55,24 @@ public static class Directory
Delete(target_path);
else throw new Exception($"directory {target_path} already exists");
}
else Directory.Create(target_path.ParentDir());
else Create(target_path.ParentDir());
System.IO.Directory.Move(current_path.Str, target_path.Str);
}
/// удаляет папку со всеми подпапками и файлами
public static void Delete(IOPath dir) =>
public static void Delete(IOPath dir) =>
System.IO.Directory.Delete(dir.Str, true);
public static IOPath[] GetFiles(IOPath dir) =>
public static IOPath[] GetFiles(IOPath dir) =>
IOPath.ArrayCast(System.IO.Directory.GetFiles(dir.Str), true);
public static IOPath[] GetFiles(IOPath dir, string searchPattern) =>
public static IOPath[] GetFiles(IOPath dir, string searchPattern) =>
IOPath.ArrayCast(System.IO.Directory.GetFiles(dir.Str, searchPattern), true);
public static IOPath[] GetDirectories(IOPath dir) =>
public static IOPath[] GetDirectories(IOPath dir) =>
IOPath.ArrayCast(System.IO.Directory.GetDirectories(dir.Str), true);
public static IOPath[] GetDirectories(IOPath dir, string searchPattern) =>
public static IOPath[] GetDirectories(IOPath dir, string searchPattern) =>
IOPath.ArrayCast(System.IO.Directory.GetDirectories(dir.Str, searchPattern), true);
/// выдает список всех файлов
@@ -86,9 +87,9 @@ public static class Directory
all_subdirs = new List<IOPath>();
return GetAllFiles_internal(dir, all_subdirs);
}
private static List<IOPath> GetAllFiles_internal(IOPath dir, List<IOPath> all_subdirs)
private static List<IOPath> GetAllFiles_internal(IOPath dir, List<IOPath>? all_subdirs)
{
bool rememberSubdirs = all_subdirs is not null;
var all_files = new List<IOPath>();
IOPath[] cur_files = GetFiles(dir);
for (int i = 0; i < cur_files.Length; i++)
@@ -96,20 +97,12 @@ public static class Directory
IOPath[] cur_subdirs = GetDirectories(dir);
for (int i = 0; i < cur_subdirs.Length; i++)
{
if(rememberSubdirs)
all_subdirs.Add(cur_subdirs[i]);
all_subdirs?.Add(cur_subdirs[i]);
all_files.AddRange(GetAllFiles_internal(cur_subdirs[i], all_subdirs));
}
return all_files;
}
public static string GetCurrent() => System.IO.Directory.GetCurrentDirectory();
public static void CreateSymlink(string sourcePath, string symlinkPath)
{
if (symlinkPath.Contains(Path.Sep))
Create(Path.ParentDir(symlinkPath));
if (!Symlink.CreateSymbolicLink(symlinkPath, sourcePath, Symlink.SymlinkTarget.Directory))
throw new InvalidOperationException($"some error occured while creating symlink\nDirectory.CreateSymlink({symlinkPath}, {sourcePath})");
}
}
public static IOPath GetCurrent() => new IOPath(System.IO.Directory.GetCurrentDirectory(), true);
}

View File

@@ -5,14 +5,14 @@ namespace DTLib.Filesystem;
public static class EmbeddedResources
{
public static Stream GetResourceStream(string resourcePath, Assembly assembly = null)
public static Stream GetResourceStream(string resourcePath, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
return assembly.GetManifestResourceStream(resourcePath)
?? throw new Exception($"embedded resource <{resourcePath}> not found in assembly {assembly.FullName}");
}
public static byte[] ReadBynary(string resourcePath, Assembly assembly = null)
public static byte[] ReadBynary(string resourcePath, Assembly? assembly = null)
{
// is required to get the Assembly called ReadBynary
// otherwise Assembly.GetCallingAssembly() in GetResourceStream will get DTLib assembly always
@@ -22,14 +22,14 @@ public static class EmbeddedResources
}
public static string ReadText(string resourcePath, Assembly assembly = null)
public static string ReadText(string resourcePath, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
using var reader = new StreamReader(GetResourceStream(resourcePath, assembly));
return reader.ReadToEnd();
}
public static void CopyToFile(string resourcePath, string filePath, Assembly assembly = null)
public static void CopyToFile(string resourcePath, string filePath, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
using var file = File.OpenWrite(filePath);

View File

@@ -1,40 +1,38 @@
namespace DTLib.Filesystem;
using FileMode = System.IO.FileMode;
using FileAccess = System.IO.FileAccess;
namespace DTLib.Filesystem;
public static class File
{
/// возвращает размер файла в байтах
public static long GetSize(IOPath file) => new System.IO.FileInfo(file.Str).Length;
public static bool Exists(IOPath file)
{
if (System.IO.File.Exists(file.Str)) return true;
return false;
}
public static bool Exists(IOPath file) => System.IO.File.Exists(file.Str);
/// если файл не существует, создаёт файл с папками из его пути и закрывает этот фвйл
public static void Create(IOPath file)
{
if (Exists(file)) return;
Directory.Create(file.ParentDir());
using System.IO.FileStream stream = System.IO.File.Create(file.Str);
stream.Close();
}
public static void Copy(IOPath srcPath, IOPath newPath, bool overwrite)
{
if (Exists(newPath))
{
if(overwrite) System.IO.File.Delete(newPath.Str);
if (overwrite)
Delete(newPath);
else throw new Exception($"file <{newPath}> alredy exists");
}
else Directory.Create(newPath.ParentDir());
using var srcFile=System.IO.File.Open(srcPath.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
using var newFile=System.IO.File.Open(newPath.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
using var srcFile = OpenRead(srcPath);
using var newFile = OpenWrite(newPath);
srcFile.CopyTo(newFile);
srcFile.Close();
newFile.Flush();
newFile.Close();
}
public static void Move(IOPath current_path, IOPath target_path, bool overwrite)
@@ -46,6 +44,7 @@ public static class File
else throw new Exception($"file {target_path} already exists");
}
else Directory.Create(target_path.ParentDir());
System.IO.File.Move(current_path.Str, target_path.Str);
}
@@ -53,62 +52,81 @@ public static class File
public static byte[] ReadAllBytes(IOPath file)
{
using System.IO.FileStream stream = OpenRead(file);
int size = GetSize(file).ToInt();
byte[] output = new byte[size];
if (stream.Read(output, 0, size) < size)
throw new Exception("can't read all bytes");
stream.Close();
return output;
}
public static string ReadAllText(IOPath file) => ReadAllBytes(file).BytesToString(StringConverter.UTF8);
public static async Task<byte[]> ReadAllBytesAsync(IOPath file)
{
using System.IO.FileStream stream = OpenRead(file);
int size = GetSize(file).ToInt();
byte[] output = new byte[size];
if (await stream.ReadAsync(output, 0, size).ConfigureAwait(false) < size)
throw new Exception("can't read all bytes");
return output;
}
public static string ReadAllText(IOPath file) =>
ReadAllBytes(file).BytesToString(StringConverter.UTF8);
public static async Task<string> ReadAllTextAsync(IOPath file) =>
(await ReadAllBytesAsync(file)).BytesToString(StringConverter.UTF8);
public static void WriteAllBytes(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenWrite(file);
stream.Write(content, 0, content.Length);
stream.Close();
}
public static async Task WriteAllBytesAsync(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenWrite(file);
await stream.WriteAsync(content, 0, content.Length);
}
public static void WriteAllText(IOPath file, string content) => WriteAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static void WriteAllText(IOPath file, string content) =>
WriteAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static async Task WriteAllTextAsync(IOPath file, string content) =>
await WriteAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
public static void AppendAllBytes(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenAppend(file);
stream.Write(content, 0, content.Length);
stream.Close();
}
public static async Task AppendAllBytesAsync(IOPath file, byte[] content)
{
using System.IO.FileStream stream = OpenAppend(file);
await stream.WriteAsync(content, 0, content.Length);
}
public static void AppendAllText(IOPath file, string content) => AppendAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static void AppendAllText(IOPath file, string content) =>
AppendAllBytes(file, content.ToBytes(StringConverter.UTF8));
public static async Task AppendAllTextAsync(IOPath file, string content) =>
await AppendAllBytesAsync(file, content.ToBytes(StringConverter.UTF8));
public static System.IO.FileStream OpenRead(IOPath file)
{
if (!Exists(file))
if (!Exists(file))
throw new Exception($"file not found: <{file}>");
return System.IO.File.Open(file.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
return System.IO.File.Open(file.Str, FileMode.Open, FileAccess.Read,
System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete);
}
public static System.IO.FileStream OpenWrite(IOPath file)
{
if (Exists(file))
Delete(file);
Create(file);
return System.IO.File.Open(file.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
}
public static System.IO.FileStream OpenAppend(IOPath file)
{
Create(file);
return System.IO.File.Open(file.Str, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite);
Directory.Create(file.ParentDir());
return System.IO.File.Open(file.Str, FileMode.Create, FileAccess.Write, System.IO.FileShare.Read);
}
public static void CreateSymlink(IOPath sourcePath, IOPath symlinkPath)
public static System.IO.FileStream OpenAppend(IOPath file)
{
if (symlinkPath.Contains(Path.Sep))
Directory.Create(symlinkPath.ParentDir());
if (!Symlink.CreateSymbolicLink(symlinkPath.Str, sourcePath.Str, Symlink.SymlinkTarget.File))
throw new InvalidOperationException($"some error occured while creating symlink\nFile.CreateSymlink({symlinkPath}, {sourcePath})");
Directory.Create(file.ParentDir());
return System.IO.File.Open(file.Str, FileMode.Append, FileAccess.Write, System.IO.FileShare.Read);
}
}
}

View File

@@ -1,4 +1,4 @@
#if NETSTANDARD2_1 || NET6_0 || NET7_0 || NET8_0
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
#define USE_SPAN
#endif
@@ -15,43 +15,54 @@ public readonly struct IOPath
public IOPath(char[] path, bool separatorsFixed=false)
{
if (path.Length == 0)
throw new Exception("path is null or empty");
Str = separatorsFixed ? new string(path) : FixSeparators(path);
}
public IOPath(string path, bool separatorsFixed=false)
public IOPath(string path, bool separatorsFixed=false) : this(path.ToCharArray(), separatorsFixed)
{
if (path.IsNullOrEmpty())
throw new Exception("path is null or empty");
Str = separatorsFixed ? path : FixSeparators(path.ToCharArray());
}
static string FixSeparators(char[] path)
{
int length = path.Length;
if (path[length-1] == Path.Sep || path[length-1] == Path.NotSep)
char lastChar = path[path.Length - 1];
if (lastChar == Path.Sep || lastChar == Path.NotSep)
length--; // removing trailing sep
char[] fixed_path = new char[length];
StringBuilder sb = new StringBuilder();
bool prevWasSeparator = false;
for (int i = 0; i < length; i++)
{
if (path[i] == Path.NotSep)
fixed_path[i] = Path.Sep;
else fixed_path[i] = path[i];
{
// prevent double separators like this "a//b"
if(!prevWasSeparator)
sb.Append(Path.Sep);
prevWasSeparator = true;
}
else
{
sb.Append(path[i]);
prevWasSeparator = false;
}
}
return new string(fixed_path);
return sb.ToString();
}
public static IOPath[] ArrayCast(string[] a, bool correct_separators=false)
public static IOPath[] ArrayCast(string[] a, bool separatorsFixed=false)
{
IOPath[] b = new IOPath[a.Length];
for (int i = 0; i < a.Length; i++)
b[i] = new IOPath(a[i], correct_separators);
b[i] = new IOPath(a[i], separatorsFixed);
return b;
}
public static IOPath[] ListCast(IList<string> a, bool correct_separators=false)
public static IOPath[] ListCast(IList<string> a, bool separatorsFixed=false)
{
IOPath[] b = new IOPath[a.Count];
for (int i = 0; i < a.Count; i++)
b[i] = new IOPath(a[i], correct_separators);
b[i] = new IOPath(a[i], separatorsFixed);
return b;
}
@@ -63,7 +74,7 @@ public readonly struct IOPath
public static explicit operator string(IOPath p) => p.Str;
public override string ToString() => Str;
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
if (obj is null) return false;
return Str == obj.ToString();

View File

@@ -1,56 +0,0 @@
namespace DTLib.Filesystem;
//
// некоторые старые методы, которые хорошо бы вырезать
//
public static class OldFilework
{
// чтение параметров из конфига
public static string ReadFromConfig(string configfile, string key)
{
lock (new object())
{
key += ": ";
using var reader = new System.IO.StreamReader(configfile);
while (!reader.EndOfStream)
{
string st = reader.ReadLine();
if (st.StartsWith(key))
{
string value = "";
for (int i = key.Length; i < st.Length; i++)
{
if (st[i] == '#')
return value;
if (st[i] == '%')
{
bool stop = false;
string placeholder = "";
i++;
while (!stop)
{
if (st[i] == '%')
{
stop = true;
value += ReadFromConfig(configfile, placeholder);
}
else
{
placeholder += st[i];
i++;
}
}
}
else
value += st[i];
}
reader.Close();
//if (value.NullOrEmpty()) throw new System.Exception($"ReadFromConfig({configfile}, {key}) error: key not found");
return value;
}
}
reader.Close();
throw new Exception($"ReadFromConfig({configfile}, {key}) error: key not found");
}
}
}

View File

@@ -5,8 +5,8 @@ namespace DTLib.Filesystem;
public static class Path
{
public static readonly char Sep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '\\' : '/';
public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\' ;
public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\';
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfEscapes(this IOPath path)
@@ -14,7 +14,7 @@ public static class Path
if (path.Str.Contains(".."))
throw new Exception($"path <{path}> uses <..>, that's not allowed");
}
/// Replaces characters restricted in filesystem path
public static IOPath ReplaceRestrictedChars(string str)
{
@@ -25,16 +25,21 @@ public static class Path
char c = r[i];
switch (c)
{
case '\n': case '\r':
case ':': case ';':
case '\n':
case '\r':
case ':':
case ';':
break;
case '/': case '\\':
case '/':
case '\\':
b.Append('-');
break;
case '<': case '>':
case '?': case '|':
case '<':
case '>':
case '?':
case '|':
b.Append('_');
break;
break;
case '"':
b.Append('\'');
break;
@@ -46,22 +51,23 @@ public static class Path
break;
}
}
return new IOPath(b.ToString(), true);
}
#if !USE_SPAN
private static void CopyTo(this string s, char[] b, int startIndex)
#if !USE_SPAN
private static void CopyTo(this string s, char[] b, int startIndex)
{
for (int i = 0; i < s.Length; i++)
b[startIndex+i] = s[i];
b[startIndex + i] = s[i];
}
#endif
public static IOPath Concat(params IOPath[] parts)
{
var needSeparator = new bool[parts.Length-1];
var needSeparator = new bool[parts.Length - 1];
int lengthSum = 0;
for (int i = 0; i < parts.Length-1; i++)
for (int i = 0; i < parts.Length - 1; i++)
{
lengthSum += parts[i].Length;
if (!parts[i].Str.EndsWith(Sep) && !parts[i + 1].Str.StartsWith(Sep))
@@ -71,46 +77,59 @@ public static class Path
}
else needSeparator[i] = false;
}
lengthSum += parts[parts.Length-1].Length;
lengthSum += parts[parts.Length - 1].Length;
var buffer = new char[lengthSum];
parts[0].Str.CopyTo(buffer, 0);
int copiedChars = parts[0].Length;
for (int i = 1; i < parts.Length; i++)
{
if (needSeparator[i-1])
if (needSeparator[i - 1])
buffer[copiedChars++] = Sep;
parts[i].Str.CopyTo(buffer, copiedChars);
copiedChars += parts[i].Length;
}
return new IOPath(new string(buffer), true);
}
/// returns just dir name or file name with extension
public static IOPath LastName(this IOPath path)
{
if(path.Length < 2)
return path;
int i = path.LastIndexOf(Sep);
if (i == path.Length - 1) // ends with separator
i = path.LastIndexOf(Sep, i-1);
if (i == -1) return path;
return path.Substring(i+1);
i = path.LastIndexOf(Sep, i - 1);
if (i == -1)
return path;
return path.Substring(i + 1);
}
public static IOPath Extension(this IOPath path)
{
int i = path.LastIndexOf('.');
if (i == -1) return LastName(path);
if (i == -1)
return LastName(path);
return path.Substring(i + 1);
}
public static IOPath RemoveExtension(this IOPath path)
{
int i = path.LastIndexOf('.');
if (i > 0)
return path.Substring(0, i);
return path;
}
public static IOPath ParentDir(this IOPath path)
{
int i = path.LastIndexOf(Sep);
if (i == path.Length - 1) // ends with separator
i = path.LastIndexOf(Sep, i-1);
i = path.LastIndexOf(Sep, i - 1);
if (i == -1) // no parent dir
return $".{Sep}";
return path.Remove(i+1);
return path.Remove(i + 1);
}
public static IOPath ReplaceBase(this IOPath path, IOPath baseDir, IOPath otherDir)
@@ -124,6 +143,6 @@ public static class Path
{
if (!path.StartsWith(baseDir))
throw new Exception($"path <{path}> doesnt starts with <{baseDir}");
return path.Substring(baseDir.Length+1);
return path.Substring(baseDir.Length + 1);
}
}

View File

@@ -1,15 +0,0 @@
using System.Runtime.InteropServices;
namespace DTLib.Filesystem;
internal class Symlink
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern bool CreateSymbolicLink(string symlinkName, string sourceName, SymlinkTarget type);
internal enum SymlinkTarget
{
File,
Directory
}
}

View File

@@ -1,55 +0,0 @@
using System.Security.Cryptography;
namespace DTLib;
//
// хеширует массивы байтов алшоритмом SHA256 и файлы алгоримом XXHash32
//
public class Hasher
{
readonly HashAlgorithm sha256 = SHA256.Create();
readonly HashAlgorithm xxh32 = XXHash32.Create();
// хеш массива
public byte[] Hash(byte[] input) => sha256.ComputeHash(input);
// хеш из двух массивов
public byte[] Hash(byte[] input, byte[] salt)
{
var rez = new List<byte>();
rez.AddRange(input);
rez.AddRange(salt);
return sha256.ComputeHash(rez.ToArray());
}
// хеш двух массивов зацикленный
public byte[] HashCycled(byte[] input, byte[] salt, ushort cycles)
{
for (uint i = 0; i < cycles; i++)
{
input = Hash(input, salt);
}
return input;
}
// хеш зацикленный
public byte[] HashCycled(byte[] input, ushort cycles)
{
for (uint i = 0; i < cycles; i++)
{
input = Hash(input);
}
return input;
}
// хеш файла
public byte[] HashFile(IOPath filename)
{
using System.IO.FileStream fileStream = File.OpenRead(filename);
//var then = DateTime.Now.Hour * 3600 + DateTime.Now.Minute * 60 + DateTime.Now.Second;
byte[] hash = xxh32.ComputeHash(fileStream);
//var now = DateTime.Now.Hour * 3600 + DateTime.Now.Minute * 60 + DateTime.Now.Second;
//InternalLog.Log($"xxh32 hash: {hash.HashToString()} time: {now - then}");
fileStream.Close();
return hash;
}
}

9
DTLib/InitSetterFix.cs Normal file
View File

@@ -0,0 +1,9 @@
// включает init и record из c# 9.0
#if NETSTANDARD
using System.ComponentModel;
namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
public class IsExternalInit { }
}
#endif

View File

@@ -2,23 +2,16 @@ namespace DTLib.Logging;
public class DefaultLogFormat : ILogFormat
{
public bool PrintTimeStamp { get; set; }
public bool PrintContext { get; set; }
public bool PrintSeverity { get; set; }
public DefaultLogFormat(bool printTimeStamp = true, bool printContext = true, bool printSeverity = true)
{
PrintTimeStamp = printTimeStamp;
PrintContext = printContext;
PrintSeverity = printSeverity;
}
public bool PrintTimeStamp { get; set; } = true;
public bool PrintContext { get; set; } = true;
public bool PrintSeverity { get; set; } = true;
public string TimeStampFormat { get; set; } = MyTimeFormat.ForText;
public string CreateMessage(string context, LogSeverity severity, object message)
{
var sb = new StringBuilder();
if (PrintTimeStamp)
sb.Append('[').Append(DateTime.Now.ToString(MyTimeFormat.ForText)).Append(']');
sb.Append('[').Append(DateTime.Now.ToString(TimeStampFormat)).Append(']');
if (PrintContext && PrintSeverity)
sb.Append('[').Append(context).Append('/').Append(severity.ToString()).Append(']');
else if(PrintContext)
@@ -28,7 +21,6 @@ public class DefaultLogFormat : ILogFormat
if (sb.Length != 0)
sb.Append(": ");
sb.Append(message);
sb.Append('\n');
return sb.ToString();
}
}

View File

@@ -2,9 +2,5 @@ namespace DTLib.Logging;
public interface ILogFormat
{
bool PrintTimeStamp { get; set; }
bool PrintContext { get; set; }
bool PrintSeverity { get; set; }
string CreateMessage(string context, LogSeverity severity, object message);
}

View File

@@ -0,0 +1,9 @@
namespace DTLib.Logging;
public enum LogSeverity
{
Debug,
Info,
Warn,
Error
}

View File

@@ -1,22 +1,14 @@
namespace DTLib.Logging;
public enum LogSeverity
{
Debug,
Info,
Warn,
Error
}
internal static class LogSeverityHelper
{
public static bool CheckSeverity(this ILogger logger, LogSeverity severity)
=> severity switch
{
LogSeverity.Debug => logger.DebugLogEnabled,
LogSeverity.Info => logger.InfoLogEnabled,
LogSeverity.Warn => logger.WarnLogEnabled,
LogSeverity.Error => logger.ErrorLogEnabled,
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, "unknown severity")
};
namespace DTLib.Logging;
public static class LogSeverityHelper
{
public static bool CheckSeverity(this ILogger logger, LogSeverity severity)
=> severity switch
{
LogSeverity.Debug => logger.DebugLogEnabled,
LogSeverity.Info => logger.InfoLogEnabled,
LogSeverity.Warn => logger.WarnLogEnabled,
LogSeverity.Error => logger.ErrorLogEnabled,
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, "unknown severity")
};
}

View File

@@ -1,4 +1,4 @@
using DTLib.Ben.Demystifier;
using DTLib.Demystifier;
namespace DTLib.Logging;
@@ -22,7 +22,7 @@ public static class LoggerExtensions
public static void LogWarn(this ILogger logger, string context, object message)
=> logger.Log(context, LogSeverity.Warn, message);
/// uses Ben.Demystifier to serialize exception
/// uses Demystifier to serialize exception
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogWarn(this ILogger logger, string context, Exception ex)
=> logger.Log(context, LogSeverity.Warn, ex.ToStringDemystified());
@@ -32,7 +32,7 @@ public static class LoggerExtensions
=> logger.Log(context, LogSeverity.Error, message);
/// uses Ben.Demystifier to serialize exception
/// uses Demystifier to serialize exception
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogError(this ILogger logger, string context, Exception ex)
=> logger.Log(context, LogSeverity.Error, ex.ToStringDemystified());

View File

@@ -11,8 +11,8 @@ public class CompositeLogger : ILogger
set
{
_debugLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].DebugLogEnabled = value;
foreach (var childLogger in ChildLoggers)
childLogger.DebugLogEnabled = value;
}
}
@@ -22,8 +22,8 @@ public class CompositeLogger : ILogger
set
{
_infoLogEnabled = true;
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].InfoLogEnabled = value;
foreach (var childLogger in ChildLoggers)
childLogger.InfoLogEnabled = value;
}
}
@@ -33,8 +33,8 @@ public class CompositeLogger : ILogger
set
{
_warnLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].WarnLogEnabled = value;
foreach (var childLogger in ChildLoggers)
childLogger.WarnLogEnabled = value;
}
}
@@ -44,46 +44,38 @@ public class CompositeLogger : ILogger
set
{
_errorLogenabled = value;
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].ErrorLogEnabled = value;
foreach (var childLogger in ChildLoggers)
childLogger.ErrorLogEnabled = value;
}
}
public ILogFormat Format { get; set; }
public readonly List<ILogger> ChildLoggers;
protected ILogger[] _loggers;
private bool _debugLogEnabled =
#if DEBUG
true;
#else
false;
#endif
private bool _debugLogEnabled;
private bool _infoLogEnabled = true;
private bool _warnLogEnabled = true;
private bool _errorLogenabled = true;
public CompositeLogger(ILogFormat format, params ILogger[] loggers)
public CompositeLogger(ILogFormat format, params ILogger[] childLoggers)
{
Format = format;
_loggers = loggers;
ChildLoggers = new List<ILogger>(childLoggers);
}
public CompositeLogger(params ILogger[] loggers) : this(new DefaultLogFormat(), loggers)
public CompositeLogger(params ILogger[] childLoggers) : this(new DefaultLogFormat(), childLoggers)
{}
public void Log(string context, LogSeverity severity, object message, ILogFormat format)
{
if(!this.CheckSeverity(severity))
return;
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].Log(context, severity, message, format);
foreach (var childLogger in ChildLoggers)
childLogger.Log(context, severity, message, format);
}
public void Dispose()
{
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].Dispose();
foreach (var childLogger in ChildLoggers)
childLogger.Dispose();
}
}

View File

@@ -5,12 +5,7 @@ namespace DTLib.Logging;
// вывод лога в консоль и файл
public class ConsoleLogger : ILogger
{
public bool DebugLogEnabled { get; set; } =
#if DEBUG
true;
#else
false;
#endif
public bool DebugLogEnabled { get; set; } = false;
public bool InfoLogEnabled { get; set; } = true;
public bool WarnLogEnabled { get; set; } = true;
public bool ErrorLogEnabled { get; set; } = true;
@@ -32,7 +27,7 @@ public class ConsoleLogger : ILogger
var msg = format.CreateMessage(context, severity, message);
lock (consolelocker)
ColoredConsole.Write(ColorFromSeverity(severity),msg);
ColoredConsole.WriteLine(msg, fg: ColorFromSeverity(severity));
}
private static ConsoleColor ColorFromSeverity(LogSeverity severity)

View File

@@ -34,7 +34,7 @@ public class ContextLogger : ILogger
public void LogWarn(object message)
=> ParentLogger.LogWarn(Context, message);
/// uses Ben.Demystifier to serialize exception
/// uses Demystifier to serialize exception
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogWarn(Exception ex)
=> ParentLogger.LogWarn(Context, ex);
@@ -43,7 +43,7 @@ public class ContextLogger : ILogger
public void LogError(object message)
=> ParentLogger.LogError(Context, message);
/// uses Ben.Demystifier to serialize exception
/// uses Demystifier to serialize exception
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogError(Exception ex)
=> ParentLogger.LogError(Context, ex);

View File

@@ -2,12 +2,7 @@
public class FileLogger : ILogger
{
public bool DebugLogEnabled { get; set; } =
#if DEBUG
true;
#else
false;
#endif
public bool DebugLogEnabled { get; set; } = false;
public bool InfoLogEnabled { get; set; } = true;
public bool WarnLogEnabled { get; set; } = true;
public bool ErrorLogEnabled { get; set; } = true;
@@ -20,7 +15,7 @@ public class FileLogger : ILogger
{
Format = format;
LogfileName = logfile;
LogfileStream = File.OpenAppend(logfile);
LogfileStream = File.OpenWrite(logfile);
}
public FileLogger(IOPath logfile) : this(logfile, new DefaultLogFormat())
@@ -41,16 +36,18 @@ public class FileLogger : ILogger
var msg = format.CreateMessage(context, severity, message);
lock (LogfileStream)
{
LogfileStream.FluentWrite(msg.ToBytes(StringConverter.UTF8)).Flush();
LogfileStream.FluentWriteString(msg)
.FluentWriteByte('\n'.ToByte())
.Flush();
}
}
public virtual void Dispose()
{
try
try
{
LogfileStream?.Flush();
LogfileStream?.Dispose();
LogfileStream.Flush();
LogfileStream.Dispose();
}
catch (ObjectDisposedException) { }
}

View File

@@ -0,0 +1,44 @@
namespace DTLib.Logging;
public interface ILineWriter : IDisposable
{
public void WriteLine(string msg);
}
public class LineWriterLogger : ILogger
{
public bool DebugLogEnabled { get; set; } = false;
public bool InfoLogEnabled { get; set; } = true;
public bool WarnLogEnabled { get; set; } = true;
public bool ErrorLogEnabled { get; set; } = true;
public ILogFormat Format { get; set; }
private readonly ILineWriter _lineWriter;
public LineWriterLogger(ILineWriter lineWriter, ILogFormat format)
{
_lineWriter = lineWriter;
Format = format;
}
public LineWriterLogger(ILineWriter lineWriter)
: this(lineWriter, new DefaultLogFormat())
{}
public void Log(string context, LogSeverity severity, object message, ILogFormat format)
{
if(!this.CheckSeverity(severity))
return;
var msg = format.CreateMessage(context, severity, message);
lock (_lineWriter)
_lineWriter.WriteLine(msg);
}
public void Dispose()
{
_lineWriter.Dispose();
}
~LineWriterLogger() => Dispose();
}

View File

@@ -4,4 +4,6 @@ public static class MyTimeFormat
{
public const string ForFileNames="yyyy.MM.dd_HH-mm-ss_zz";
public const string ForText="yyyy.MM.dd HH:mm:ss zz";
public const string TimeOnly="HH:mm:ss";
public const string DateOnly="yyyy.MM.dd";
}

View File

@@ -58,15 +58,20 @@ public class FSP
return fileStream.ToArray();
}
private static readonly byte[] fspHeaderStrBytes = "FSP_file_size:".ToBytes();
public void DownloadToStream(System.IO.Stream fileStream)
{
lock (MainSocket)
{
BytesDownloaded = 0;
Filesize = BitConverter.ToInt64(MainSocket.GetPackage(), 0);
var fspHeader = MainSocket.GetPackage();
if (!fspHeader.StartsWith(fspHeaderStrBytes))
throw new Exception(
$"invalid fsp header: '{fspHeader.BytesToString()}' " +
$"({fspHeader.MergeToString(',')})");
Filesize = BitConverter.ToInt64(fspHeader, fspHeaderStrBytes.Length);
if (Filesize < 0)
throw new Exception("FileSize < 0");
MainSocket.SendPackage("ready");
while (BytesDownloaded < Filesize)
{
@@ -89,8 +94,11 @@ public class FSP
Filesize = fileStream.Length;
lock (MainSocket)
{
MainSocket.SendPackage(BitConverter.GetBytes(Filesize));
MainSocket.GetAnswer("ready");
byte[] filesizeBytes = BitConverter.GetBytes(Filesize);
byte[] fspHeader = new byte[fspHeaderStrBytes.Length + filesizeBytes.Length];
fspHeaderStrBytes.CopyTo(fspHeader, 0);
filesizeBytes.CopyTo(fspHeader, fspHeaderStrBytes.Length);
MainSocket.SendPackage(fspHeader);
while (BytesUploaded < Filesize)
{

View File

@@ -1,39 +1,58 @@
using System.Threading;
namespace DTLib;
namespace DTLib;
//
// простой и понятный класс для выполнения каких-либо действий в отдельном потоке раз в некоторое время
//
public class Timer
public class Timer : IDisposable
{
Task TimerTask;
bool Repeat;
CancellationTokenSource кансель = new();
private Task? _timerTask;
private CancellationTokenSource _cts = new();
private bool _repeat;
private int _delayMs;
private Action _action;
// таймер сразу запускается
public Timer(bool repeat, int delay, Action method)
public Timer(bool repeat, int delayMs, Action action)
{
Repeat = repeat;
TimerTask = new Task(() =>
{
do
{
if (кансель.Token.IsCancellationRequested)
return;
Task.Delay(delay).Wait();
method();
} while (Repeat);
});
_repeat = repeat;
_delayMs = delayMs;
_action = action;
}
public void Start() => TimerTask.Start();
public void InvokeAction()
{
_action();
}
public void Start()
{
_timerTask = new Task(Loop);
_timerTask.Start();
}
// завершение потока
public void Stop()
{
Repeat = false;
кансель.Cancel();
_cts.Cancel();
_cts = new CancellationTokenSource();
}
public void Dispose()
{
Stop();
}
private void Loop()
{
var ct = _cts.Token;
do
{
if (ct.IsCancellationRequested)
return;
Task.Delay(_delayMs).Wait();
if (ct.IsCancellationRequested)
return;
InvokeAction();
} while (_repeat);
}
}

Some files were not shown because too many files have changed in this diff Show More